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
as/400, os/400, iseries, system i, i5/os, ibm i, power systems, 6.1, 7.1, V7,
LATEST COMMENTS
MC Press Online