What's New in CL Programming

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

In the last two releases, IBM has made huge and unprecedented changes in the CL programming language. Until V5R3 and V5R4, CL seemed about as dormant as DOS! But now, we can all thank IBM for beefing up this language and providing us with many great features. These enhancements improve CL's ability to interoperate with other languages and generally make CL easier and more productive to work with.

These are some of the enhancements:

  • Support for integer and unsigned integer data types
  • Support for pointer data types
  • Select
  • DoUntil loop
  • DoWhile loop
  • DoFor loop
  • Subroutines
  • Support for processing up to five files

Integers and Unsigned Integers

These new data types provide an efficient method of storing certain numeric data. Obviously, if the data contains decimal values, then these two data types are inappropriate, but otherwise, they provide an even more compact form than the traditional packed (*DEC) format. The following chart illustrates the maximum values expressed by the *INT, *UINT, and *DEC formats (for comparison).

Relative Data Type Size and Maximum Values

Data Type
Size in Bytes
Maximum Value
Integer
*INT
2
32,000
Unsigned Integer
*UINT
2
64,000
Packed
*DEC
2
999
Integer
*INT
4
2 Billion
Unsigned Integer
*UINT
4
4 Billion
Packed
*DEC
4
99,999


The code statements below show the definition of the new integer data types with the traditional DCL statement.

DCL         &INT1         *INT   2   VALUE(-1000) 
DCL         &INT2         *INT   4   VALUE(-100000)
DCL         &INT3         *UINT  2   VALUE(1000)
DCL         &INT4         *UINT  4   VALUE(100000)

A simple example of when to use one of these is any time that you need a "counter" or "index" variable. In the example shown below in Figure 1, the program receives a file name as a parameter; it attempts to create an achieved copy of the object and then deletes the original. If it fails to get a lock, it retries up to three times before giving up.

PGM  PARM(&FILE &LIB)                            
     DCL &FILE *CHAR (10)                          
     DCL &LIB  *CHAR (10)                         
     DCL &X    *UINT (2)                        
                              
      CRTDUPOBJ  OBJ(&FILE) FROMLIB(&LIB) OBJTYPE(*FILE) +   
                 TOLIB(ARCHIVE) DATA(*YES)             
      MONMSG MSGID(CPF0000) EXEC(DO)                  
             DLTF ARCHIVE/&FILE              
             CRTDUPOBJ  OBJ(&FILE) FROMLIB(&LIB) OBJTYPE(*FILE) + 
             TOLIB(ARCHIVE) DATA(*YES)                     
      ENDDO                                                
                               
DLT:  DLTF &LIB/&FILE                                            
      MONMSG MSGID(CPF0000) EXEC(DO)                             
             CHGVAR &X (&X +1)                                    
             IF COND(&X >= 3) THEN(DO)                         
                 SNDUSRMSG  MSG('File' *BCAT &FILE *BCAT 'in library' +
                          *BCAT &LIB *BCAT 'Was not destroyed.') +    
                          MSGTYPE(*INFO) TOUSR(*REQUESTER)           
                 GOTO END                                     
             ENDDO                                        
             GOTO DLT                                     
      ENDDO                                             
                                                           
      SNDUSRMSG  MSG('File' *BCAT &FILE *BCAT 'in library ' + 
                 *BCAT &LIB *BCAT 'destroyed.') +            
                 MSGTYPE(*INFO) TOUSR(*REQUESTER)           
                                 
END:         ENDPGM                         

Figure 1: Archive and delete the selected file.

Pointers

Pointers are fairly new for most RPG programmers, but they have been around for a long time and are commonly used in other languages, such as C. Unlike most other data types, pointers do not contain data. Instead, they contain the memory address for a piece of data. Each pointer is 16 bytes long, which corresponds to the size of a memory address.

These sophisticated variables might seem out of place in CL programs, which are typically fairly simple programs. However, for greater interoperability with other languages and APIs, pointers provide much-needed flexibility.

The example shown in Figure 2 illustrates how to use pointers to scan through a large text field holding the current user portion of the library list and searching for a specific library.

PGM                                                                 
     DCL &LIBL *CHAR (2750)                                         
     DCL &PTR *PTR ADDRESS(&LIBL)                                   
     DCL &LIB *CHAR (10) STG(*BASED) BASPTR(&PTR)                   
                                                                    
             RTVJOBA    USRLIBL(&LIBL)                              
                                                                    
CHECK:       IF COND(&LIB = 'EUREKA') THEN(DO)          
             SNDUSRMSG  MSG('I Found it!') +                        
                        MSGTYPE(*INFO) TOUSR(*REQUESTER)            
                        GOTO END                                    
             ENDDO                                                  
                                                                    
             CHGVAR VAR(%OFS(&PTR)) VALUE(%OFS(&PTR) + 11)          
             GOTO CHECK                                             
END:         ENDPGM                    

Figure 2: Search the library list.

The field &PTR is defined as a *PTR data type and is initialized with the address of &LIBL. The variable &LIB is "based" on &PTR. This means that it does not have its own location in storage (memory). Rather, it moves to the location given in &PTR. When this program loads, &LIB initially overlays the first 10 bytes of &LIBL. Each time the program loops, the pointer moves 11 characters farther down the LIBL field. The offset function (%OFS) allows the CL program to easily adjust the location of the pointer by simply adding or subtracting a number of bytes. The program uses an increment of 11 because there is one byte of blank data between each library name in the list, and each library name is 10 characters long, so the total is 11.

SELECT Statement

The SELECT statement is nothing more than a specialized "IF" structure. It defines a set of mutually exclusive tests. Each test has a corresponding command to execute upon a successful test. As soon as that command is complete, control passes to the statement after the ENDSELECT.

So while this does not present any truly new ability, it does provide a much easier to read and understand logical control for CL programs. Figure 3 illustrates the use of the select statement by sending users to a different menu based upon their user class.

PGM                                                       
     DCL &CLASS *CHAR (10)                                
                                                          
             RTVUSRPRF  USRCLS(&CLASS)                    
                                                          
             SELECT                                       
             WHEN COND(&CLASS = '*SECOFR') THEN( +        
                  GOTO UBERLEET)                         
             WHEN COND(&CLASS = '*SECADM') THEN( +        
                  GOTO NOTSOUBER)                         
             WHEN COND(&CLASS = '*SYSOPR') THEN( +        
                  GOTO REALWORK)                          
             WHEN COND(&CLASS = '*PGMR') THEN( +          
                  GOTO GEEKDOM)                           
             OTHERWISE CMD(GOTO LOCKDOWN)                          
             ENDSELECT  
END:         ENDPGM     

Figure 3 : Send the user to the correct menu.

DOUNTIL Statement

The DOUNTIL command provides a nice, easy-to-use tool for looping until some event happens. The example in Figure 4 shows how to use DOUNTIL to force a program to loop continuously until the job, subsystem, or the system itself is shut down.

PGM                                          
     DCL &ENDJOB *LGL               
                                                         
             DOUNTIL COND(&ENDJOB)           
                  CALL PGM1                  
                  CALL PGM2                  
                  CALL PGM3 
RTVJOBA    ENDSTS(&ENDJOB)                       
             ENDDO                           
                                             
END:         ENDPGM                            

Figure 4: Loop through Pgm1, Pgm2, and Pgm3 until EOJ.

It's important to note that DOUNTIL does not test the value of &ENDJOB until after the code inside the loop has processed once. The test is performed when control of the program reaches the ENDDO for the loop.

DOWHILE Statement

If DOUNTIL's test at the end of the loop bugs you, you could use DOWHILE instead. It is similar in function. However, it loops while the condition is true and exits the loop when it is not true. And the test is performed at the beginning of the loop, so there is no guarantee that the code inside the loop will ever execute.

PGM                                          
     DCL &ENDJOB *LGL                        
                                             
             RTVJOBA    ENDSTS(&ENDJOB)      
                                             
             DOWHILE COND(*NOT &ENDJOB)           
                  CALL PGM1                  
                  CALL PGM2                  
                  CALL PGM3 
             RTVJOBA    ENDSTS(&ENDJOB                 
             ENDDO                           
                                             
END:         ENDPGM                            

Figure 5: Loop through Pgm1, Pgm2, and Pgm3 until EOJ.

DOFOR Statement

If you have a loop that executes a specific number of times, DOFOR will come in handy. Figure 6 illustrates a modified version of the program shown in Figure 1. This version uses DOFOR to control how many times the delete retries.

PGM  PARM(&FILE &LIB)      
     DCL &FILE *CHAR (10)            
     DCL &LIB  *CHAR (10)     
     DCL &X    *INT  (2)                 
     DCL &ERR  *LGL 

      CRTDUPOBJ  OBJ(&FILE) FROMLIB(&LIB) OBJTYPE(*FILE) +       
                 TOLIB(ARCHIVE) DATA(*YES)                     
      MONMSG MSGID(CPF0000) EXEC(DO)                             
             DLTF ARCHIVE/&FILE                                   
             CRTDUPOBJ  OBJ(&FILE) FROMLIB(&LIB) OBJTYPE(*FILE) + 
             TOLIB(ARCHIVE) DATA(*YES)                     
      ENDDO                                                
            CHGVAR VAR(&ERR) VALUE(‘1’)          
      DOFOR VAR(&X) FROM(1) TO(3) BY(1)
            DLTF &LIB/&FILE         
            MONMSG MSGID(CPF0000) EXEC(ITERATE)
CHGVAR VAR(&ERR) VALUE(‘0’)                             
            LEAVE                       
      ENDDO                                        
      IF COND(&ERR) THEN( +                         
                 SNDUSRMSG  MSG('File' *BCAT &FILE *BCAT 'in library' +
                          *BCAT &LIB *BCAT 'Was not destroyed.') +    
                          MSGTYPE(*INFO) TOUSR(*REQUESTER))
ELSE CMD( +
      SNDUSRMSG  MSG('File' *BCAT &FILE *BCAT 'in library ' + 
                 *BCAT &LIB *BCAT 'destroyed.') +            
                 MSGTYPE(*INFO) TOUSR(*REQUESTER))          
                         
END:         ENDPGM              

Figure 6: Archive and delete the selected file.

Subroutines

OK, is it just me, or is it incredible that IBM has added subroutine support to CL programs? While it's true that a lot of CL programs are far too simple to benefit from subroutines, they're still very useful in plenty of places. Figure 7 shows the code for a program that calls several programs and monitors for errors in each one. A subroutine provides common error-handling for the program.

PGM                                                         
     DCL &MSG *CHAR (80)
     DCL &ANS *CHAR (1)                     
     DCL &X   *INT  (2)                                     
                                                            
     DOUNTIL (&X = 1)                                       
             CHGVAR &X 1                                    
             CALL PGM1                                      
             MONMSG MSGID(CPF0000) EXEC(CALLSUBR ERR)       
     ENDDO                                                  
                                                            
     DOUNTIL (&X = 2)                                       
             CHGVAR &X 2                                    
             CALL PGM2                                      
             MONMSG MSGID(CPF0000) EXEC(CALLSUBR ERR)       
     ENDDO                                                  
     DOUNTIL (&X = 3)                                  
             CHGVAR &X 3                       
             CALL PGM3                            
             MONMSG MSGID(CPF0000) EXEC(CALLSUBR ERR)    
     ENDDO                                   
                       
  SUBR     SUBR(ERR)             
                        
             RCVMSG     MSG(&MSG) 
             SNDUSRMSG  MSGID(CPF9897) MSGF(QCPFMSG) MSGDTA(&MSG) + 
                          VALUES(C I D R) DFT(R) TOMSGQ(*SYSOPR) + 
                          MSGRPY(&ANS)     
                         
             SELECT       
             WHEN (&ANS = C) THEN(+        
             SNDPGMMSG  MSGID(CPF9897) MSGF(QCPFMSG) MSGDTA(&MSG) +
                          MSGTYPE(*ESCAPE))          
             WHEN (&ANS = R) THEN(+                                
             CHGVAR &X 0)                                          
             WHEN (&ANS = D) THEN(DO)                              
             DUMPCLPGM                                             
             SNDPGMMSG  MSGID(CPF9897) MSGF(QCPFMSG) MSGDTA(&MSG) +
                          MSGTYPE(*ESCAPE))                        
             ENDDO                                                 
             ENDSELECT                                             
                                                                   
  ENDSUBR                                                          
END:         ENDPGM                                                

Figure 7: Provide error-handling.

Notice that the calls to PGM1, PGM2, and PGM3 are all wrapped up in a Do loop. That loop allows the retry function of the error-handling subroutine to work. If an error occurs and the user takes an "R" to retry, &X is set to 0 and the loop will continue calling the program again. If no error occurs or if a different option is taken, &X will be unchanged, ending the loop.

Multiple Files

Another great addition to CL is the ability to handle more than one file in a single program. Sure, the CL language is not intended for intensive I/O-handling programs; however, sometimes it's convenient to read two or more files in the same program. Figure 8 shows a CL program that searches a list of libraries for source physical files and then performs a FNDSTRPDM command against each of these files, looking for a specified string.

PGM          PARM(&TEXT)                                           
             DCLF       FILE(APPLIBS) OPNID(LIBS)                  
             DCLF       FILE(QAFDBASI) OPNID(FILES)                
             DCL        VAR(&TEXT) TYPE(*CHAR) LEN(40)             
             
     /* LOOP THROUGH EACH LIBRARY IN THE APPLIBS FILE */    
     /* AND ADD ALL OF FILES IN EACH TO QGPL/FILES    */          
             DOWHILE    COND(1 = 1)                                
             RCVF       OPNID(LIBS)                                
             MONMSG MSGID(CPF0864) EXEC(LEAVE)                     
                                                                   
             DSPFD      FILE(&LIBS_LIBNAME/*ALL) TYPE(*BASATR) +   
                          OUTPUT(*OUTFILE) OUTFILE(QTEMP/FILES) +  
                          OUTMBR(*FIRST *ADD)                      
             ENDDO                                                 

     /* READ EACH FILE IN QGPL/FILES AND PERFORM A FNDSTRPDM */    
     /* AGAINST IT.  SEARCHING FOR THE PARM &TEXT            */     
             OVRDBF     FILE(QAFDBASI) TOFILE(QTEMP/FILES)         
             DOWHILE    COND(1 = 1)                                
             RCVF       OPNID(FILES)          
             MONMSG MSGID(CPF0864) EXEC(LEAVE)       
             IF (&FILES_ATDTAT = 'S' *AND +       
                 &FILES_ATFTYP = 'P') THEN(DO)      
             FNDSTRPDM  STRING(&TEXT) +      
                          FILE(&FILES_ATLIB/&FILES_ATFILE) + 
                          MBR(*ALL) OPTION(*NONE) PRTMBRLIST(*YES) +
                          PRTRCDS(*NONE)    
             MONMSG     MSGID(PDM0000)                
             ENDDO      
             ENDDO      
             DLTOVR     FILE(QAFDBASI)           
                                
ENDPGM                                                               

Figure 8: Read data from multiple files.

Each of the declare files have an OPNID parameter (LIBS and FILES, respectively) that provides a unique identifier for the files. The RCV command should reference the appropriate file ID to ensure reading the next record from the appropriate file. Each of the fields from the files must also use the open ID as a prefix to the field name, such as &FILES_ATFILE. The field name is ATFILE, and the open ID is FILES, so the variable name in the CL program is &FILES_ATFILE. If you are using only one file in the CL program, you can omit the open ID from the RCV command and the field names.

Pleasant Surprises

While it is doubtful that many shops will jump to V5R4 just to get these new features in CL, it's still true that most shops will be happy to take advantage of these enhancements as they move up to the current release. The enhancements to CL in the last two releases also leave us wondering what pleasant surprises might be in store for the next release.

Kevin Forsythe is the author of the new book SQL for eServer i5 and iSeries. He has over 18 years of experience working with the iSeries platform and its predecessors. He has been a member of the DMC team for the past nine years. Kevin's primary responsibility is providing iSeries education, but he also provides customers with project management, system design, analysis, and technical construction. In addition to his technical skills (RPG IV, CL, OS/400, SQL, FTP, Query, VB, Net.Data), Kevin possesses the ability to communicate new and complex concepts to his students. He has been the primary instructor for DMC's iSeries-based AS/Credentials training courses since 1997 and has authored courses such as Advanced ILE, SQL, Embedded SQL, Operations Navigator, and Intro to WebSphere Studio. An award-winning speaker, he has spoken at every COMMON Conference since the spring of 2000.

BLOG COMMENTS POWERED BY DISQUS

LATEST COMMENTS

Support MC Press Online

$

Book Reviews

Resource Center

  •  

  • LANSA Business users want new applications now. Market and regulatory pressures require faster application updates and delivery into production. Your IBM i developers may be approaching retirement, and you see no sure way to fill their positions with experienced developers. In addition, you may be caught between maintaining your existing applications and the uncertainty of moving to something new.

  • The MC Resource Centers bring you the widest selection of white papers, trial software, and on-demand webcasts for you to choose from. >> Review the list of White Papers, Trial Software or On-Demand Webcast at the MC Press Resource Center. >> Add the items to yru Cart and complet he checkout process and submit

  • SB Profound WC 5536Join us for this hour-long webcast that will explore:

  • Fortra IT managers hoping to find new IBM i talent are discovering that the pool of experienced RPG programmers and operators or administrators with intimate knowledge of the operating system and the applications that run on it is small. This begs the question: How will you manage the platform that supports such a big part of your business? This guide offers strategies and software suggestions to help you plan IT staffing and resources and smooth the transition after your AS/400 talent retires. Read on to learn: