A few years ago, I wrote about the conditional compiler directives introduced in RPG IV. IBM added these non-executable statements to RPG IV to let programmers selectively control /COPY and now /INCLUDE members--avoiding the possibility of redundantly including the same prototype. What I'm finding is that most shops using this capability are, in fact, using it in two different ways: one conventional, one very much RPG II-like in nature.
The purpose of a Compiler Directives is to control what source code is included in the compile. Compiler Directives are controlled using symbolic names. These names are, as mentioned, non-executable and don't interfere with your program variables. Basically if the symbolic name is defined, then it includes (or omits) a section of code. You can also do the reverse, if the symbolic name is not defined then include code, otherwise omit the code. The available directives are:
The /IF and /ELSEIF statements let you test whether a symbolic name has already been defined. To do this, they support the DEFINED parameter, for example:
H/IF DEFINED(*CRTBNDRPG)
H DFTACTGRP(*NO) ACTGRP('RPGOPEN')
H/endif
In this example, I'm checking if the symbol *CRTBNDRPG has been defined. If it is, then I include the DFTACTGRP and ACTGRP keywords on my Header specification.
To define symbols, the /DEFINE statement is used. To delete a symbol, the /UNDEFINE statement is used, for example:
/IF NOT DEFINED(PICKLES)
/DEFINE PICKLES
/endif
In this example, I check if the symbol PICKLES has been defined, if it has not, I define it. Fortunately, IBM creates a few symbols for us based on the compiler we're using and the operating system level. IBM-generated symbols include:
This list is in no way inclusive, but other than additional symbols for each version of the operating system, that's pretty much it.
Starting with v6.1, the traditional QC2LE binding directory is automatically included with RPG IV compiles. Specifying the BNDDIR('QC2LE') keyword is no longer necessary. But if we compile our code with TGTRLS(*PRV) or aren't yet on v6.1, we still need to specify it. The following directives help us in this situation:
/IF NOT DEFINED(*V6R1M0)
H BNDDIR('QC2LE')
/ENDIF
This is an example of testing for a symbol to be undefined. The NOT operator, obviously, reverses the IF DEFINED test, so in our example, if we are not on 6.1 (or later) QC2LE is included. Note that the operating system symbols are always defined on their release and later releases, hence they always test true on later releases. Therefore if IBM introduces a feature in 6.1 that isn't on 5.4, you can wrap it in a /IF DEFINED(*V6R1M0) test and when you move to the next release, it will still resolve to true and include that 6.1-compatible code.
The following directives would all test true if we were compiling for 5.4:
/IF DEFINED(*V4R2M0)
/IF DEFINED(*V5R1M0)
/IF DEFINED(*V5R2M0)
/IF DEFINED(*V5R4M0)
This is not a complete list, obviously; any version/release at or below the target release will prove true. Another way to say is that as long as the target release is greater than or equal to the version being tested, it will test true.
The only other two IBM-provided symbols are *CRTBNDRPG and *CRTRPGMOD. These are used to test which version of the compiler is being used. As I indicated earlier, these are useful for including or omitting the DFTACTGRP keyword, but not much else.
Testing for release boundaries or which compiler was used is a necessary feature, but certainly not the only purpose of Compiler Directives. User-defined symbols help you control the inclusion of sections of code. Traditionally these directives have been used to control /COPY members--specifically prototypes. In fact, the C and C++ languages heavily use their equivalent of /INCLUDE (named #include in C/C++) to control redundant prototyping. Like RPG IV, C and C++ allow recursive include members (members that themselves contain other /INCLUDE statements).
The problem with recursive includes is that if you store a set of prototypes in that include member and that same member is included as a nested member somewhere else, it could end up being included in your program source multiple times. This is where the /IF compiler directive and user-defined symbols can help.
Here is an example source member containing some of the RPG OPEN service program subprocedure prototypes:
/IF NOT DEFINED(RPGOPEN_ICOMP)
/DEFINE RPGOPEN_ICOMP
** Copyright('RPG OPEN - (c) 2009 Robert Cozzi, Jr. All rights reserved.')
** Released Under OSL v3.0 - www.opensource.org/licenses/osl-3.0.php
D iComp PR N extProc('RPGOPEN_icomp')
/if DEFINED(*V5R3M0)
D compVal1 * Value options(*STRING:*TRIM)
D compVal2 * Value options(*STRING:*TRIM)
/else
D compVal1 * Value options(*STRING)
D compVal2 * Value options(*STRING)
/endif
/ENDIF
Notice that the first line, above, is the /IF NOT DEFINED(RPGOPEN_ICOMP). This statement has a corresponding /ENDIF statement at the bottom of the source member. Effectively what this says is that if the user-defined symbol (and by "user-defined" I mean "RPG IV Programmer defined") RPGOPEN_ICOMP is not defined, continue including this source member. The first thing included is the /DEFINE RPGOPEN_ICOMP statement. The symbol RPGOPEN_ICOMP is defined and therefore will cause the first statement to fail on subsequent encounters. So if the ICOMP source member is included multiple times, the prototypes will only be included the first time--avoiding duplicate declarations of its prototypes.
One thing RPG has over other languages is the /EOF directive; which was provided to give us a shortcut. Instead of inserting a /IF as the top line in the source member and a corresponding /ENDIF as the final line in the source member, we can incorporate /EOF and consolidate these statements into one location in our source member, here's how:
/IF DEFINED(RPGOPEN_ICOMP)
/EOF
/ENDIF
/DEFINE RPGOPEN_ICOMP
** Copyright('RPG OPEN - (c) 2009 Robert Cozzi, Jr. All rights reserved.')
** Released Under OSL v3.0 - www.opensource.org/licenses/osl-3.0.php
D iComp PR N extProc('RPGOPEN_icomp')
/if DEFINED(*V5R3M0)
D compVal1 * Value options(*STRING:*TRIM)
D compVal2 * Value options(*STRING:*TRIM)
/else
D compVal1 * Value options(*STRING)
D compVal2 * Value options(*STRING)
/endif
With this modification in place, maintaining the source member is a bit easier. Instead of trying to avoid stepping on that final /ENDIF statement, we can add new content to the source member (at the bottom or top) and not worry about the closing /ENDIF statement. This is because of incorporating /EOF when RPGOPEN_ICOMP is defined. Basically we say, if the symbol is already defined, end-of-file. The /EOF means "end of file" so no additional lines of the source member are included. If, however, the RPGOPEN_ICOMP symbol is not yet defined, the /EOF is skipped and the next line to be interpreted is the /DEFINE RPGOPEN_ICOMP, which obviously defines the symbol. Any subsequent includes of this source member, in this compile, will avoid repeated declarations of the prototypes included in this source member.
This kind of feature really shines when you have a collection of prototypes, named constants or other statements in the source member. One that I have that has a lot of data structure templates and prototypes is my APIPROTOS (IBM i OS API Prototypes) source member. By adding those 3 simple statements to the beginning of the source member, I avoid repeated including of the prototypes--and of course avoid those compile-time errors.
Having written C and C++ and used their #include function for a couple of decades now, I know how programmers of those languages have standardized the use of include members for prototypes and structures. The standards used world-wide are basically the same as I've illustrated here. Certainly since C/C++ do not have the /EOF, they tend to use the full /IF ... /ENDIF control block (wrapping the entire source member), but the results are the same. Therefore, the recommended way to avoid redundant prototype declarations is to use /DEFINE mbrName at the beginning of the source member, as follows:
/IF DEFINED(MBRNAME)
/EOF
/ENDIF
/DEFINE MBRNAME
It is a universal practice to define a user symbols with a name that is either identical to or incorporates the member name. Often I use the term "protos" as a suffix, but it is certainly just a matter of taste. For example, if the member name is JOBLOG, I would use JOBLOG_PROTOS as the symbol name. But that's just me.
/IF DEFINED(JOBLOG_PROTOS)
/EOF
/ENDIF
/DEFINE JOBLOG_PROTOS
// The rest of the prototype include member goes here...
When storing prototypes in include members, you often have a situation where that prototype is either defined in another package or you just don't want to include it. Our C/C++ friends also have a standard for this situation. You simply wrap the specific prototype in a /IF ... /ENDIF statement, using the prototype name itself as the symbol name. Something like this:
/IF DEFINED(MATH_PROTOS)
/EOF
/ENDIF
/DEFINE MATH_PROTOS
/IF NOT DEFINED(LOG10)
/DEFINE LOG10
** Log base 10
D Log10 PR 8F
D fValue 8F Value
/ENDIF
/IF NOT DEFINED(LOG2)
/DEFINE LOG2
** Log base 2
D Log2 PR 8F
D fValue 8F Value
/ENDIF
/IF NOT DEFINED(LN)
/DEFINE LN
** Natural Logaritm base e
D LN PR 8F
D fValue 8F Value
/ENDIF
The initial /IF DEFINED(MATH_PROTOS) takes care of recursively including this source member. Then each individual prototype is wrapped in its own directive. Why do this?
If you happen to have two sets of prototypes with conflicting names, such as in any of the following situations:
Of course all of these situations apply to Data Structures (i.e., templates) as well as prototypes. So if you have for example, a prototype for a subprocedure named JOBLOG and you download and install the RPG Open service program (which also includes a JOBLOG subprocedure) you can avoid name collision using this technique.
Recently I've seen a handful of shops trying to implement this capability in their own applications. However, instead of avoiding duplicates by using a symbolic name, as I've illustrated above, they sometimes use a nonstandard and somewhat unintuitive approach that I think is cumbersome and less productive.
That technique is to require the programmer to include a specific /DEFINE statement for each subprocedure they want to use in their program. For example, if you wanted to use the LOG10, LOG2 and LN subprocedures you would normally code something like this:
H/IF DEFINED(*CRTBNDRPG)
H DFTACTGRP(*NO)
H/ENDIF
D/include mylib/qcpysrc,math
D** My program source continues here...
Instead of the above, the alternative techniques asks you to do this:
H/IF DEFINED(*CRTBNDRPG)
H DFTACTGRP(*NO)
H/ENDIF
/DEFINE LOG10
/DEFINE LOG2
/DEFINE LN
D/include mylib/qcpysrc,math
D** My program source continues here...
Note the addition of the 3 /DEFINE statements. If we wanted to use the GETGMT prototype, then we'd have to include yet another /DEFINE. This occurs because the prototypes are set up as follows:
/IF DEFINED(LOG10)
D Log10 PR 8F
D fValue 8F Value
/ENDIF
/IF DEFINED(LOG2)
D Log2 PR 8F
D fValue 8F Value
/ENDIF
/IF DEFINED(LN)
D LN PR 8F
D fValue 8F Value
/ENDIF
Looking at the MATH source member, above, the code looks clearer. But so what? You as an applications programmer will virtually never see this code. Wouldn't it be easier to simply include the /INCLUDE MATH directive, instead of having to (A) Know which prototype to include and then (B) the name of the directive that allows me to include the prototype.
Clearly this style is implemented by someone who hasn't programmed in languages that have had /INCLUDE and /DEFINE before. It reminds me of all those standard service program or subprocedure "templates" I see in shops--templates that were written by the first person to try to create a subprocedure or perhaps a service program. In almost every case, those templates are like looking a crayon drawing compared to the Mona Lisa.
Simply because someone was the first one to get something to compile (probably via the "hunt and peck" method of programming) doesn't mean it should become a shop standard. I don't mean it's the end of the world if its implemented this way, but you can sure see it from here.
SPECIAL OFFER: Through December 31, 2009 if you order RPG xTools for just $495, you'll get a free copy of Cozzi's Subprocedures and Service Programs Training DVD.
iWeekly, Bob's weekly audio podcast is now available in the Apple iTunes store. Search "iweekly". You can also watch Bob as he records iWeekly and streams video of it, live every Friday at Noon eastern on RPG World.
Follow Bob Cozzi on Twitter at: Twitter.com/bobcozzi
Follow RPG World on Twitter: Twitter.com/rpgworld
Bob Cozzi is the author of Subprocedures and Service Programs. A 3-disc training series available on DVD that gives RPG IV programmers an easy way to learn RPG IV Subprocedures and Service Programs. It is available now at www.RPGWorld.com/DVD. Bob's website www.RPGWorld.com is also the place to download the source code featured in RPG Coder and his Tuesday Tips video podcast along with additional examples he's created over the decades. Join Bob Cozzi & Friends in 2010 for "RPG World" The RPG Developers Conference, and Bob's weekly video podcast iWeekly is where you'll learn about everything from RPG IV to SQL, XML, PHP and even a few things you didn't want to know.