RPG IV at IBM i 7.1
Date Posted: April 15, 2010 12:00 AM
Author: Bob Cozzi

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:

  1. SORTA Supports Ascend and Descend Extenders
  2. Sort Data Structure Arrays based on Subfields
  3. Search Data Structure Arrays with Lookup Functions
  4. Scan and Replace Built-in Function
  5. Modification to %LEN
  6. Long Externally Described Field Subfield Names (DDS ALIAS name support)
  7. Improved Subprocedure Return Value Design
  8. Parameter Ordinal Built-in Function
  9. Prototypes Are Now Optional When Not Needed
  10. More Granular Control on XML-INTO (PTF v6r1)
  11. RPG IV Storage Model Now Follows that of ILE CL
  12. Teraspace Storage option for %ALLOC Built-in Function
  13. DBGVIEW Can now be Encrypted
  14. 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 subprocedures—but 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 prototype—even 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 everyone—making 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:

  1. datasubf
  2. 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 item—you 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.


Want to use this article? Click here for options!
Want to subscribe? Click here!
  • BobCozzi
    2 years ago
    Apr 26, 2010

    Before you ask: "Yes there should be a QUALIFIED keyword on the Data Structure array where the DIM(50) Keyword is specified in this article."

    -Bob Cozzi
    www.RPGWorld.com

  • You must log on before posting a comment.

    Are you a new visitor? Register Here
     

    around the forums

    better data access for AS400 applications
    Forum Name: Systems Management
    21 May 2012 06:22 AM | Replies: 0
    Selection error involving field *N.
    Forum Name: SQL, Query and Database
    18 May 2012 02:19 PM | Replies: 6
    WINDOWS 7 with CLIENT ACCESS 7 R1
    Forum Name: Communications/Networking
    18 May 2012 08:43 AM | Replies: 1

    ProVIP Sponsors

    BCD

    Join Our Community!

    Subscribe today to iPro Developer! iPro Developer is packed with technical know-how for developers of IBM i, iSeries, AS400 and System i. Sign up now to get your full subscriber benefits including:

    • Code available for download
    • Full access to the online article archive (including all System iNEWS ProVIP content)
    • Downloadable ebook with past 6 months of articles
    • Discounts on eLearning classes, self-paced training, in-person events, and more!
    iPro Developer Newsletters
    • Get the Latest News
    • Product Updates
    • Helpful Tricks
    • Productivity Tips