RPG IV at IBM i 7.1
Normally when a new release comes out there's a handful of enhancements to
our beloved RPG IV language. This time it's no different. Well, maybe a little
different: there are over a dozen new features or changes to the RPG IV
language with IBM i 7.1. Depending on your skill level and shop's evolution
into advanced RPG IV, you might find four to eight of them interesting. But there are a
few that you should adopt regardless of your skill level or maturity of your
code base.
You know me, I like to cut to the chase, so here are the new features in RPG
IV, summarized for quick scaning:
- SORTA Supports Ascend and Descend Extenders
- Sort Data Structure Arrays based on Subfields
- Search Data Structure Arrays with Lookup Functions
- Scan and Replace Built-in Function
- Modification to %LEN
- Long Externally Described Field Subfield Names (DDS ALIAS name support)
- Improved Subprocedure Return Value Design
- Parameter Ordinal Built-in Function
- Prototypes Are Now Optional When Not Needed
- More Granular Control on XML-INTO (PTF v6r1)
- RPG IV Storage Model Now Follows that of ILE CL
- Teraspace Storage option for %ALLOC Built-in Function
- DBGVIEW Can now be Encrypted
- UCS-2 Parameters are Interchangeable with Character Parameters
Let me explain each of these new features and give you an example of how to
use them.
SORTA Supports Ascend/Descend Extenders
The SORTA opcode has been able to sort arrays in the order in which they were
previously declared. The ascending and descending sequencing was controlled with
the ASCEND and DESCEND keywords on the array declaration (D-spec). With this new
enhancement, an operation extender can be specified to control the ordering. Use
SORTA(A) to sort the array in ascending sequence and SORTA(D) to sort the array
in descending sequence. The ASCEND and DESCEND keywords are effectively
ignored when these new operation extenders are used.
// Sort Array
D myStuff S 7P 2 Dim(50)
/free
sorta(d) myStuff; // Sort the array in descending order
sorta(a) myStuff; // Sort the array in ascending order
sorta myStuff; // Sort the array based on the ASCEND or DESCEND keywords
/end-free
- Usefulness Score: 6 out of 10
- Implementation Design Score: 10 out of 10
Sort Data Structure Arrays by Subfield Name
Data Structure Arrays, or rather, data structures that have the DIM keyword
specified, may now be sorted with SORTA by specifying one of the data
structure's subfield names. This capability has always been there, but required
a trick to make it work; now it's officially supported.
// Sort Data Structure Array by Subfield
D myStuff DS Dim(50)
D Item 5A
D qty 7P 0
/free
sorta myStuff(*).item; // Sort the array by Item number.
sorta(d) myStuff(*).qty; // Sort the array in descending order by Quantity
/end-free
- Usefulness Score: 10 out of 10
- Implementation Design Score: 7 out of 10 (7 because you have to specify (*))
Search Data Structure Arrays with Lookup Functions
The %LOOKUP built-in functions have been enhanced to allow you to search an
array based on a data structure subfield. The array must, obviously, be a Data
Structure Array. To search by subfield, specify an index entry of (*) followed
by the qualified subfield name. For example, to search the MYSTRUCT array by
ITEM, the following code can be used.
// Lookup Data Structure Array Subfield
D myStruct DS Dim(50) Qualified Inz
D Item 5A
D Qty 7P 0
D Price 7P 2
D index S 10I 0
D itemCount S 10I 0
/free
index = %lookup('12345' : myStruct(*).item : 1 : itemCount);
- Usefulness Score: 8 out of 10
- Implementation Design Score: 7 out of 10 (7 because you have to specify (*))
Scan and Replace Built-in Function
About 14 years ago, I consolidated the %SCAN built-in function with the
%REPLACE built-in function and created a subprocedure named FindReplace. This
subprocedure appeared in RPG Coder a few years ago and has been part of RPGLIB
for more than a decade. When IBM introduced %REPLACE, I felt it was a necessary
function, but what people really needed/wanted was a FindReplace function
similar to what they were using in products such as Microsoft Word. Sadly,
%REPLACE only replaced data in a string, didn't search for and then replace it.
The new %SCANRPL (scan and replace) built-in function is effectively a clone
of my FindReplace subprocedure. You specify the data to find (known as the
"pattern"), the data to replace the pattern in the search-data, and the
search-data. All instances of the scan pattern in the searched data are replaced
with one operation. Here's the syntax diagram:
%SCANRPL(scan-pattern : replacement-text : search-data { : start { : length } )
Scan-pattern is the data to search for. Replacement-text is the text that
will replace the scan-pattern in the search-data. Search-data is, obviously, the
data to be searched. And start and length are the position within the
search-data to search and the number of bytes to search, respectively.
To retrieve the modified value, assign the %SCANRPL to a new field or the
same field as that being scanned.
/free
myName = %scanrpl('Bob' : 'Robert' : myName );
- Usefulness Score: 8 out of 10
- Implementation Design Score: 6 out of 10 (case-sensitive only, name of
function is not intuitive)
Modification to %LEN
A second parameter has been added to %LEN when used with character variables
that include the VARYING keyword. Normally when used with VARYING field, %LEN
returns the current length of the VARYING length field. The old %SIZE built-in
function returned the declared length plus either 2 or 4 bytes because it
includes the hidden "current length" attribute. The second parameter of %LEN, *MAX
can be used to finally get the declared length of a VARYING length field.
// %LEN *MAX parameter option
D company S 50A Varying
D curLen S 10I 0
D maxLen S 10I 0
D size S 10I 0
/free
company = 'RPG World';
curLen = %len(company); // curLen = 9
maxLen = %len(company:*MAX); // maxLen = 50
size = %size(company); // size = 52
- Usefulness Score: 8 out of 10
- Implementation Design Score: 7 out of 10 (Because it is so rarely
needed, this task could have been a new built-in function, %MAXLEN or
%DCLLEN or similar)
Long Externally Described Field Subfield Names (DDS ALIAS name support)
This is one of those features that people have been asking for since RPG III:
the ability to use the ALIAS names from DDS in RPG. Well, that day is finally
here. Specifying the ALIAS keyword on a File Description specification and
then using that file's record format name on the LIKEREC causes the data
structure to use the alias names for its subfields instead of the short, 1 to
10 position names currently used.
If you're a fan of the EXTNAME (externally described data structure) keyword,
you'll be happy to know that ALIAS is also supported with EXTNAME.
To use this capability, you need to include the ALIAS keyword in your file's
DDS. Moving forward, this may be helpful, but for now, since 99.999% of all DDS
does not include this keyword...
The ALIAS keyword in DDS is very anal. The keyword must be in UPPERCASE and
long field names must be in ALL UPPERCASE or it will not work. Here's an example
DDS of a sales summary file:
A R SALESREC
A CUSTNO 7P 0 ALIAS(CUSTOMER_NUMBER)
A CUSTNAME 32A ALIAS(COMPANY_NAME)
A MTHSALES 7P 2 ALIAS(MONTHLY_SALES_TOTAL)
A REGION 2A
A K CUSTNO
In RPG IV, to use the long field name identified by the ALIAS keyword in the
DDS, you have two options:
Option 1: Using ALIAS with File Description Specifications
// Using the ALIAS keyword to access long field names
// Also see the CUSTSALES DDS source member
FCUSTSALES IF E K DISK ALIAS
D Sales1 DS LikeRec(SaleRec) Inz
In the above example, the ALIAS keyword does not cause the Input fields to
be the longer name, but rather opens up the use of the LIKEREC keyword. When
used to define a Data Structure, the LikeRec keyword uses the ALIAS names for
that file. If ALIAS and PREFIX are used together, then you can rename/map the
input fields to the ALIAS field names as follows:
// Using the ALIAS keyword to access long field names
// Also see the CUSTSALES DDS source member
FCUSTSALES IF E K DISK ALIAS PREFIX('S.')
D S DS LikeRec(SaleRec) Inz
Option 2: Using ALIAS with Externally Described Data Structure
D Sales2 E DS extName(CUSTSALES) Qualified Inz
D ALIAS
If you like the legacy EXTNAME keyword, then you'll enjoy the new ALIAS
keyword that may be used with it to define a data structure. Simply add ALIAS
when using EXTNAME and the long field names are brought into the program instead
of the standard name.
Remember, however, if the ALIAS keyword is not used in the DDS, the original
field names are brought in by default.
- Usefulness Score: 3 out of 10
- Implementation Design Score: 1 out of 10 (The ALIAS keyword should have
been only on the Data Structure. Putting it on the F-spec will mislead
people into believing the Input spec field names will use the ALIAS when
they do not.)
Improved Subprocedure Return Value Design
I spoke with IBMers on several occasions expressing concern about using long
return values with subprocedures. Doing something as simple as converting lower
to upper case introduced a performance issue, as its return value was often
longer than a subprocedure could handle efficiently. Remember, this is for the return
value, not parameters passed to a subprocedure, so it impacts only a small
portion of subproceduresbut impacts them greatly.
The new RTNPARM keyword changes how lengthy return values are implemented.
When RTNPARM is specified, the return value is passed as a hidden parameter
rather than on the stack. This improves performance significantly. Use this
keyword when you have a return value from a subprocedure that is character
and declared with a length of 10 bytes or more. In other words, always use it
for Character return values (including VARYING). This could have just been a
default for 7.1 and later, but now we need to use a keyword. Which means many
people just won't use it.
- Usefulness Score: 8 out of 10
- Implementation Design Score: 5 out of 10 (This should have just
worked if you target 7.1 or later. Now another confusing keyword
needs to be used, sometimes. Argh!)
Parameter Ordinal Built-in Function
If you have ever written a subprocedure and used one of those CEEDOD or CEESGI
APIs, you know passing the number of the parameter to those APIs is fun.
Starting with 7.1, instead of specifying the parameter number,
you can pass %PARMNUM(parmName) and the compiler will insert
the parameter number. For example, to call CEESGI previous, you had to code
this:
// WITHOUT using the %PARMNUM built-in function
P myProc B EXPORT
D myProc PI 1024A OPDESC RTNPARM
D firstName 256A Const Varying
D lastName 256A Const Varying
D Middle 256A Const Varying OPTIONS(*NOPASS)
D nLen S 10I 0
D maxRtnLen S 10I 0
D dataType S 10I 0
D curLen S 10I 0
D maxLen S 10I 0
/free
if (%parms() >= 1);
ceegsi(1 : dataType : curlen : maxLen: *OMIT);
rtnName = %subst(firstName:1:curLen);
endif;
if (%parms() >= 3);
ceegsi(3 : dataType : curlen : maxLen: *OMIT);
rtnName += ' ' + %subst(Middle:1:curLen);
endif;
if (%parms() >= 2);
ceegsi(2 : dataType : curlen : maxLen: *OMIT);
rtnName += ' ' + %subst(lastName:1:curLen);
endif;
return rtnName;
/end-free
With 7.1 you can code it like this:
// Using the %PARMNUM built-in function
P myProc B EXPORT
D myProc PI 1024A OPDESC RTNPARM
D firstName 256A Const Varying
D lastName 256A Const Varying
D Middle 256A Const Varying OPTIONS(*NOPASS)
D nLen S 10I 0
D maxRtnLen S 10I 0
D dataType S 10I 0
D curLen S 10I 0
D maxLen S 10I 0
/free
>> if (%parms() >= %PARMNUM(firstName));
>> ceegsi(%PARMNUM(firstName) : dataType : curlen : maxLen: *OMIT);
rtnName = %subst(firstName:1:curLen);
endif;
>> if (%parms() >= %PARMNUM(middle));
>> ceegsi(%PARMNUM(middle) : dataType : curlen : maxLen: *OMIT);
rtnName += ' ' + %subst(Middle:1:curLen);
endif;
>> if (%parms() >= %PARMNUM(lastName));
>> ceegsi(%PARMNUM(lastName) : dataType : curlen : maxLen: *OMIT);
rtnName += ' ' + %subst(lastName:1:curLen);
endif;
return rtnName;
/end-free
The lines marked with ">>" have the %PARMNUM built-in function being used.
This new function allows you to identify the parameters by parameter name
instead of their relative position in the list of parameters.
- Usefulness Score: 10 out of 10
- Implementation Design Score: 10 out of 10
Prototypes Are Now Optional When Not Needed
Perhaps the single biggest complaint about using subprocedures with RPG IV
has been the need to also declare a prototypeeven when you are not calling the
subprocedure. This became immediately obvious on version 6.1 to those who
attempted to use the new MAIN keyword. You had to create
both a Procedure Interface and a Prototype for the program in order to avoid the
RPG cycle overhead. This extra effort put off virtually everyonemaking the
MAIN keyword one of the least-used features ever introduced to RPG.
If you have a SUBPROCX defined in a source member and nowhere in that
source member do you call SUBPROCX, prior to 7.1 you had to include the
Prototype for SUBPROCX in that source member or it wouldn't compile. The same
is true when using the MAIN keyword with the prototype for a program.
All those anal retentive restrictions are gone with 7.1 and later,
but one new one was introduced.
When using the MAIN keyword, a prototype is no longer necessary. Instead, you
specify the name of the subprocedure that is the entry procedure for the
program. If the procedure has parameters, you must include the EXTPGM keyword on
the "Procedure" Interface. This is new and not at all intuitive. But at least you don't have
to code the Prototype. If, however, there are no parameters, and you do not code
a Procedure Interface, the EXTPGM keyword is not needed. Again this is a bit too
much standing on one foot while holding the other up while trying to catch a
baseball. So we'll see if it gets adopted. It may simply be because the cycle
is removed, but IBM could have done a better job of design.
The other aspect of this is, of course, when creating a service program and
there are several subprocedures in a source member, but nowhere in that same source
member are the subprocedures called. In this situation you no longer have to
include the Prototypes for those uncalled subprocedures. However, when those
subprocedures are eventually called by programs elsewhere, the prototype
is required.
- Usefulness Score: 10 out of 10
- Implementation Design Score: 4 out of 10
More Granular Control on XML-INTO (PTF 6.1)
A couple of PTFs that have been available for months are now officially
documented in the RPG IV 7.1 implementation. IBM added the ability to more
easily parse XML using XML-INTO. This change virtually eliminates the need to
ever use XML-SAX. Some bit fiddlers out there that love the complexity of XML-SAX
will, I'm sure, continue to advocate it over XML-INTO; I do not.
The change is the inclusion of two more options:
- datasubf
- countprefix
The datasubf option allows you to parse XML that includes "attributes".
Attributes are what IBM is calling the XML tag parameters. Here's an example:
Normal XML:
<ITEM>IBMIRD</ITEM>
XML with Attributes:
<ITEM ID="12345" PRICE="37.50">IBMIRD</ITEM>
The ID and PRICE attributes would normally confuse XML-INTO and require the
use of XML-SAX. But with the datasubf option, all that changes:
D ITEM DS Qualified Inz
D ID 5A
D price 7P 2
D description 50A Varying
D myXML S 256A Varying
/free
myXML = '<ITEM ID="12345" PRICE="37.50">IBMIRD</ITEM>';
xml-into item %xml(myXML : 'datasubf=description');
/end-free
This parses the XML so the results are:
- ITEM.id = '12345;
- ITEM.price = 37.50
- ITEM.description = 'IBMIRD'
The DESCRIPTION subfield is identified by the datasubf keyword in
the options parameter of the XML-INTO opcode. Most examples will show datasubf=value
rather than datasubf=description, but I wanted to point out that the subfield
name identified by datasubf can be any name.
Counter Prefix
The other new XML-INTO feature is the counterprefix option. This option is
used to identify the name of a subfield in your data structure that receives the
count of the number of groups the XML-INTO opcode parsed. For example, if you
have XML containing a Customer Order and each order may have one or more than one
line itemyou can parse the XML and have the count of the line items stored in
a subfield you identify with the counterprefix keyword.
This is just a prefix. It identifies the portion of the name that must also
include the actual repeating XML element. For example, if the <LINEITEM> tag
appears several times within the <ORDER> tag, then a valid option would be:
xml-into order %xml(orderXML : 'counterprefix=cnt');
In this case the ORDER data structure would have a subfield named
CNTLINEITEM. That subfield must be numeric and will receive the count of the
number of LINEITEM tags XML-INTO parses.
- Usefulness Score: 8 out of 10
- Implementation Design Score: 10 out of 10
RPG IV Storage Model Follows that of ILE CL
This one always confuses me. I thought we switched over to the Teraspace
model with RPG IV a few releases back, but I guess they just made it
teraspace-friendly. This new enhancement allows modules and
programs written with RPG IV to use the teraspace storage model or to inherit
the storage model of their caller. This is similar (identical?) to those
confusing parameters on the ILE CL compiler. Those same options are now part of the RPG IV compiler.
STGMDL Keyword - Identify the Storage Model Used for the Program
- STGMDL(*SNGLVL)Single-level storage model is used.
- STGMDL(*TERASPACE)Teraspace storage model is used.
- STGMDL(*INHERIT)Inherit the storage model from the caller.
- Usefulness Score: 7 out of 10
- Implementation Design Score: 8 out of 10 (It is a bit more confusing
than it needs to be.)
Teraspace Storage option for %ALLOC Built-in Function
The new ALLOC keyword on the Header specification controls which storage
model will be used for the %ALLOC built-in function and ALLOC opcode. When
ALLOC(*TERASPACE) is specified, up to 10 terabytes of storage may be allocated
vs the 16MB limit of the
single-level storage model ALLOC(*SNGLVL). So if you were, for example, supporting web
applications that allow file uploads beyond 16MB and were using the C runtime
TS_ALLOC, you can now use %ALLOC instead.
The ALLOC keyword supports 3 options:
- ALLOC(*SNGLVL)Single-level storage model is used.
- ALLOC(*TERASPACE)Teraspace storage model is used.
- ALLOC(*STGMDL)Inherit the storage model from the STGMDL keyword.
These options could have been a bit more clearly implemented, but with time
(a few years of using them) we will probably determine why we have STGMDL and
ALLOC keywords. One thing that is confusing is that even if STGMDL(*TERASPACE)
is specified, RPG fields, arrays, and data structures seem to be restricted to
the single-level storage model's 16MB limitation. This could just be a
documentation oversight, however, as I did not get a chance to verify this
during my test period with 7.1.
- Usefulness Score: 8 out of 10
- Implementation Design Score: 8 out of 10 (This is a bit more confusing
than it needs to be.)
DBGVIEW Can now be Encrypted
This is primarily for third-party software vendors. Instead of not shipping
source code embedded in your RPG IV programs to customers, you can now ship
encrypted debug source code that requires a password. This allows you to
perform remote debugging, specify the decryption key (i.e., password), and
debug your own code on your customer's system without worrying about
disclosing trade secrets (such as how poorly your code is written).
This option is available when DBGVIEW(*LIST) is specified when compiling the
module or program. To encrypt the debug code, specify the DBGENCKEY parameter
with the password/key used to both encrypt and decrypt the listing.
When the STRDBG command is run on the program, a prompt is displayed
requesting the decryption password.
- Usefulness Score: 5 out of 10
- Implementation Design Score: 10 out of 10
UCS-2 Parameters Are Interchangeable with Character Parameters
When a subprocedure parameter contains the CONST or VALUE keyword, the
parameter may be declared as a UCS-2 value. The value passed to that parameter
may be either a UCS-2 character value or a non-UCS-2 (normal) character value.
The compile automatically converts between UCS-2 and Character. This is also
true for the subprocedure's return value. To declare a UCS-2 parameter, specify
its data-type as "C" instead of "A".
- Usefulness Score: 4 out of 10 (few people use UCS-2. I think more people would get use from Unicode enabling RPG IV.)
- Implementation Design Score: 10 out of 10
Bob Cozzi has a new book coming out in the Summer of 2010. "Breaking Free"
Bob Cozzi's Guide to The Modern RPG Language.