The CL Corner: Still Copying Code Within a CL Program? PDF Print E-mail
Programming - CL
Written by Bruce Vining   
Friday, 28 January 2011 00:00

Support MC Press - Visit Our Sponsors

 

Forums Sponsor

POPULAR FORUMS

Forums

 

 

Search Sponsor

 

POPULAR SEARCHES

Search

 

 

Are you up to speed on IBM CL subroutine support?

 

In days long past, I used to copy small pieces of common code from one section of a program to another section. This common code might be validating input values from a user, adding X days to a date to determine a date in the future (or past), sending messages, or a wide range of other functions. I didn't want to duplicate this common logic to multiple locations within my program (especially if I might have to change the code in the future), but using GOTO to run common code and then trying to resume where I was prior to running the GOTO command can quickly become a real pain. Duplicating the code was often the lesser of the two evils. Thankfully, CL was enhanced awhile back (V5R4 to be specific) with support for subroutines. And in case you're thinking, "I know all about subroutines, I've been using them for years in RPG," I'll just caution you that CL subroutines have a few features that RPG subroutines don't. So don't stop reading just because you're familiar with subroutines in another language.

 

Using the Subroutine (SUBR) command to define the start of some common logic and the End Subroutine (ENDSUBR) command to define the end of the shared logic, you can now run this common code using the Call Subroutine (CALLSUBR) command. After the subroutine returns, the program then continues to run, resuming at the command following the CALLSUBR command. To my way of thinking, a much improved solution over using GOTOs and/or duplicating code within the program.

 

Subroutines are physically located in your source code following your main program logic and prior to the ENDPGM command. You can have many subroutines within your program, each delimited by paired SUBR and ENDSUBR commands.

 

The SUBR command has one parameter, SUBR, which is used to name the subroutine. This name is then used with the SUBR parameter of the CALLSUBR command to identify which subroutine is to be run. The ENDSUBR command defines the end of the common logic that started with the previous SUBR command. The ENDSUBR command has one parameter, RTNVAL, which can be used to specify a return value that can be returned to the caller of the subroutine. This ability to provide a return value from the subroutine is one feature that is not available in languages such as RPG, and it will be reviewed shortly. The CALLSUBR command has two parameters: SUBR, which identifies the subroutine to call, and RTNVAL, which can optionally identify a CL variable to receive the return value of the called subroutine. There is also a Return from subroutine command, RTNSUBR, which can be used to immediately return control to the calling CALLSUBR command without having to run the ENDSUBR command. The RTNSUBR command also has one parameter, RTNVAL, which, like ENDSUBR, allows you to identify a return value to be returned to the calling CALLSUBR.

 

Subroutines can be called, using CALLSUBR, from anywhere within your program except as the EXEC parameter of a program-level MONMSG command. This "call from almost anywhere" includes the ability to call subroutines while you are currently running in a subroutine. This capability is shown in the following example:

 

Pgm                                                   

Dcl        Var(&Counter)    Type(*Dec) Len(1 0)      

Dcl        Var(&CounterChr) Type(*Char) Len(1)       

Dcl        Var(&Exit)       Type(*Int)               

                                                     

DoUntil    Cond(&Exit *EQ 1)                           

           CallSubr Subr(DoCalcs) RtnVal(&Exit)      

           EndDo                                     

Return                                               

                                                     

Subr       Subr(DoCalcs)                             

           ChgVar Var(&Counter) Value(&Counter + 1)  

           CallSubr Subr(DspResults)                 

           If Cond((&Counter / 2) *EQ 2) Then( +       

              RtnSubr RtnVal(1))                      

           CallSubr Subr(DspResults)                 

EndSubr                                             

                                                    

Subr       Subr(DspResults)                         

           ChgVar Var(&CounterChr) Value(&Counter)  

           SndUsrMsg Msg(&CounterChr) MsgType(*Info)

EndSubr                                             

EndPgm                                              

 

In this example program, there are two subroutines defined. One subroutine, DoCalcs, simply adds 1 to the &Counter variable, calls the DspResults subroutine to display the value of &Counter, and checks to see if dividing &Counter by 2 results in a value of 2. If the result is 2, DoCalcs immediately exits back to the caller, using RTNSUBR, with a return value of 1. If the result is not 2, DoCalcs again calls the subroutine DspResults to display the value of &Counter and then returns to the caller using ENDSUBR with a return value of 0. The default RTNVAL value, for both ENDSUBR and RTNSUBR, is 0, so we did not need to explicitly code RTNVAL(0) on the ENDSUBR command.

 

The program itself starts off with a DOUNTIL loop conditioned by CL variable &Exit not being set to the value of 1. Within the DOUNTIL command group, the program calls the DoCalcs subroutine, identifying CL variable &Exit as the variable to receive any return value from the DoCalcs subroutine. When DoCalcs returns a value of 1, using the RTNSUBR command, the DOUNTIL command group is exited. &Exit is defined as a 4-byte integer variable, which is the required definition for subroutine return value parameters.

 

Different RTNVAL variables can be identified with the CALLSUBR command. So, while not shown in the sample program, you could at one point in the program CALLSUBR SUBR(DoCalcs) RTNVAL(&Exit) and at another point in the program CALLSUBR SUBR(DoCalcs) RTNVAL(&Y). This ability to explicitly define a return variable, external to the subroutine, can be quite useful when calling a subroutine from multiple locations within a program and not wanting to worry about saving and restoring status information across the various callers. You can also use CL variables as RTNVAL arguments with the ENDSUBR and RTNSUBR commands, so literal values such as the '1' used in the example are not required.

 

Running the earlier sample program will result in the following messages being displayed.

 

1

1

2

2

3

3

4

 

I mentioned earlier that subroutines can call other subroutines. In the case of CL, this also includes the ability for a subroutine to call itself—another difference from RPG. In the following example, the DOUNTIL loop is eliminated and DoCalcs simply calls itself until the result of dividing &Counter by 2 is 2. At this point, DoCalcs returns to its caller using the RTNSUBR command. If the caller was an earlier instance of DoCalcs, then this preceding instance will next run the ENDSUBR command, following the CALLSUBR command, and so the subroutine stack will return (eventually) to the initial CALLSUBR of the main program logic and then run the RETURN command, ending the program.

 

Pgm                                                 

Dcl        Var(&Counter)    Type(*Dec) Len(1 0)     

Dcl        Var(&CounterChr) Type(*Char) Len(1)      

                                                     

CallSubr Subr(DoCalcs)                

Return                                              

                                                    

Subr       Subr(DoCalcs)                            

           ChgVar Var(&Counter) Value(&Counter + 1) 

           CallSubr Subr(DspResults)                

           If Cond((&Counter / 2) *EQ 2) Then(RtnSubr)

           CallSubr Subr(DspResults)                  

           CallSubr Subr(DoCalcs)                     

EndSubr                                              

                                                      

Subr       Subr(DspResults)                           

           ChgVar Var(&CounterChr) Value(&Counter)    

           SndUsrMsg Msg(&CounterChr) MsgType(*Info)  

EndSubr                                               

EndPgm                                                

 

Running this version of the program will result in the same output as the previous program.

 

1

1

2

2

3

3

4

 

There is one more command related to subroutines, Declare Processing Options (DCLPRCOPT). By default, there can be up to 99 subroutines active, or nested, within a program. You can have as many subroutines defined in the program as you want (or at least I'm not aware of any set definition limit imposed by CL), but you cannot nest more than 99 CALLSUBR commands within the program's execution path. For most business applications, 99 nested CALLSUBR commands should be more than sufficient. But you can, with the DCLPRCOPT command and its Subroutine stack depth (SUBRSTACK) parameter, change the supported nesting maximum to a value within the range of 20 to 9999.

 

Subroutines represent a very flexible tool to add to your programming arsenal. They can provide reduced duplication of common code within your source and be utilized with a variety of programming styles.

More CL Questions?

Wondering how to accomplish a function in CL? Send your CL-related questions to me at bvining@brucevining.com. I'll try to answer your burning questions in future columns.

 

as/400, os/400, iseries, system i, i5/os, ibm i, power systems, 6.1, 7.1, V7,


Bruce Vining
About the Author:

Bruce Vining is president and co-founder of Bruce Vining Services, L.L.C., a firm providing contract programming and consulting services to the System i community (www.brucevining.com). He began his career in1979 as an IBM Systems Engineer in St. Louis, Missouri, and then transferred to Rochester, Minnesota, in 1985, where he continues to reside. From 1992 until leaving IBM in 2007, Bruce was a member of the System Design Control Group responsible for OS/400 and i5/OS areas such as System APIs, Globalization, and Software Serviceability. He is also the designer of Control Language for Files (CLF).

 

A frequent speaker and writer, Bruce can be reached at bvining@brucevining.com.

 

MC Press books written by Bruce Vining available now on the MC Press Bookstore.

 

IBM System i APIs at Work IBM System i APIs at Work

Master APIs with this in-depth, example-rich exploration into these powerful tools.

List Price $89.95
Now On Sale
 
Read More >>
Last Updated on Friday, 28 January 2011 00:00
 
User Rating: / 3
PoorBest 

Related Articles: