• The Four Hundred
  • Subscribe
  • Media Kit
  • Contributors
  • About Us
  • Contact
Menu
  • The Four Hundred
  • Subscribe
  • Media Kit
  • Contributors
  • About Us
  • Contact
  • V5R4 CL Enhancements, Revealed and Detailed

    February 8, 2006 Ted Holt

    Version 5 Release 4 of i5/OS continues the tradition that V5R3 began–that is, making a liar of me. Before V5R3, I would have bet my last Federal Reserve Note that IBM would never enhance the i5/iSeries Control Language (cleverly known by its acronym, CL), and I would have lost the bet. Like V5R3, V5R4 introduces several new CL features. I would like to thank Guy Vig of IBM for telling me about them. Now I’d like to share them with you.

    Enhancements come in two general areas–subroutines and data definition. Let’s learn more about them. Keep in mind that I have not tested any of the code in this article, since I don’t have access to a machine that is running the new release.

    CL Subroutines

    Most of the CL code I write does not need subroutines, yet I am very glad to see that CL now supports a subroutine structure. I expect to use subroutines primarily to divide a CL procedure (a program or a module) into tasks of logically related code. The second way I plan to use subroutines is to avoid the duplication of code within a CL procedure.

    Place subroutines at the end of a CL source member, just before the ENDPGM command. No need to add a GOTO or RETURN command before the first subroutine, the CL compiler inserts an implicit RETURN for you. Use the SUBR and ENDSUBR commands to indicate the beginning and end of a subroutine. Here is the general outline CL procedures follow.

    PGM
    
    <declarations: DCL, DCLF, DCLPRCOPT, COPYRIGHT>
    <global MONMSG commands>
    <procedural code>
    
    SUBR
    <procedural code>
    ENDSUBR
    
    SUBR
    <procedural code>
    ENDSUBR
    
    ENDPGM
    

    I would have thought that IBM would use the label to indicate the name of a subroutine, but I would have been wrong. The name of a subroutine goes in the SUBR command’s only parameter, also named SUBR. If you place a label on the SUBR command, or precede the SUBR command with a labeled null command, you may use GOTO within the subroutine to return to the first executable command within the subroutine. In the following example, branching to the Again label continues execution with the first CHGVAR command.

    Again:  SUBR SUBR(DoSomethin)
    
            chgvar    &Var1   &Var2
            chgvar    &Var3   &Var4
            call      SomePgm (&SomeVar &RtnCode)
            if        (&RtnCode *eq ' ') then(goto Again)
            <... more commands ...> 
    
            ENDSUBR
    

    Speaking of labels, I found it interesting that the compiler permits two or more labels to have the same name as long as they are not in the same routine. In the following example, three END labels exist–one in the main routine and two in subroutines Validate and NextFile.

    pgm
         <... more commands ...>
    Read:
         rcvf
         monmsg cpf0864 exec(goto end)
         <... more commands ...>
        goto Read
    End:
        return
    
    subr Validate
        <... more commands ...>
        if &ErrorFound then(goto End)   
        <... more commands ...>
    End: 
        chgvar (&ErrorCount) (&ErrorCount + 1)
    
    endsubr
    
    subr NextFile
    
        <... more commands ...>
        if &EOF then(goto End)   
        <... more commands ...>
    
    End: endsubr
    
    

    And while we’re on the subject of labels, don’t try to access a label in one routine from another routine. The compiler won’t allow it.

    The ENDSUBR command has one optional parameter, RTNVAL, which allows you to return a four-byte signed integer value to the caller. One way you might use this feature is to provide a return status, which would indicate whether the subroutine completed normally or abnormally. You might use zero, the default value, to indicate that the subroutine completed normally, and non-zero values to indicate errors. The caller can test the return value and proceed accordingly.

    The subroutine returns to the caller when the last executable instruction has run, but you may exit the subroutine earlier with the RTNSUBR command. RTNSUBR has the same optional RTNVAL parameter that ENDSUBR has.

    To invoke a subroutine, use the CALLSUBR command. CALLSUBR has two parameters. SUBR indicates the name of the subroutine to be called, while RTNVAL may contain the name of a four-byte integer variable to receive the subroutine’s return code.

    The following example may give you a clearer picture of how subroutines fit into the CL procedure model.

    This procedure calls program PGM1, which may or may not create a report. If PGM1 creates the report, then programs PGM2, PGM3, and PGM4 should also run. But if PGM1 does not build the report, the job is complete. Subroutine RtvSplfNbr uses the QSPRILSP API to retrieve the number of the last spool file that was created in the job. I used the RTNVAL parameter of the ENDSUBR command to load the retrieved spool file number into the &SPLFNBR1 and &SPLFNBR2 variables. Calling the subroutine immediately before and after calling PGM1 provides two spool file numbers. If they are the same, PGM1 did not create a spool file.

    pgm 
                                                      
       dcl   &SplfNbr1    *int    4
       dcl   &SplfNbr2    *int    4                   
                                                      
       dcl   &RcvVar        *char  70                   
       dcl     &BytesAvail  *int    4  stg(*defined) defvar(&RcvVar  1) 
       dcl     &BytesRtn    *int    4  stg(*defined) defvar(&RcvVar  5) 
       dcl     &SplfName    *char  10  stg(*defined) defvar(&RcvVar  9) 
       dcl     &JobName     *char  10  stg(*defined) defvar(&RcvVar 19) 
       dcl     &UserName    *char  10  stg(*defined) defvar(&RcvVar 29) 
       dcl     &JobNbr      *char   6  stg(*defined) defvar(&RcvVar 39) 
       dcl     &SplfNbr     *int    4  stg(*defined) defvar(&RcvVar 45) 
       dcl     &SysName     *char   8  stg(*defined) defvar(&RcvVar 49) 
       dcl     &SplfCrtDat  *char   7  stg(*defined) defvar(&RcvVar 57) 
       dcl     &SplfCrtTim  *char   6  stg(*defined) defvar(&RcvVar 65) 
    
       dcl   &RcvVarLen   *int    4    value(70)               
       dcl   &FmtName     *char  10                   
       dcl   &ErrorCode   *char   8                   
                                                      
       dcl   &Stat        *lgl                        
       dcl   &SplfExists  *lgl                        
                                                      
    
       callsubr subr(RtvSplfNbr) rtnval(&SplfNbr1)                   
       call     pgm1                                               
       callsubr subr(RtvSplfNbr) rtnval(&SplfNbr2)                   
    
    /* If pgm1 created a report, continue with other tasks. */
       if (&SplfNbr2 *ne &SplfNbr1) do  
          call pgm2                             
          call pgm3                             
          call pgm4                             
       enddo                                     
       return                                    
           
    subr subr(RtvSplfNbr) 
            
       chgvar   &BytesAvail         70 
       chgvar   &FmtName            'SPRL0100' 
       chgvar   &ErrorCode          x'0000000000000000' 
                                             
       chgvar   &SplfExists  '1'         
       call     QSPRILSP     (&RcvVar &RcvVarLen &FmtName &ErrorCode)  
       monmsg   cpf333a      exec(chgvar &SplfExists '0') 
       if (&SplfExists) then
       else do        
          chgvar   &SplfNbr        0    
       enddo                                
                                            
    endsubr rtnval(&SplfNbr) 
                               
    endpgm
    

    The Subroutine Stack

    Subroutines cannot be nested. However, subroutines can call other subroutines. In fact, a subroutine can call itself. Each invocation of a subroutine pushes an entry onto a stack that by default can go 99 levels deep. An entry is popped off the subroutine stack when the subroutine returns so, as far as I’m concerned, 99 may as well be infinity. However, you can use the new Declare Processing Options (DCLPRCOPT) command to change the depth of the subroutine stack to any value from 20 to 9,999 levels. The following command allows 180 levels of subroutine calls. If the program containing this DCLPRCOPT command were to go into a loop of subroutines calling subroutines, the CL runtime will send escape message CPF0822 (‘Subroutine stack overflow’) when the subroutine at level 180 tries to call another subroutine.

    DCLPRCOPT  SUBRSTACK(180)
    

    A CL procedure may have only one DCLPRCOPT command, which must appear in the declarations section with the other declaration commands–DCL, DCLF, and COPYRIGHT.

    Data Definition

    If you were getting tired of the Declare (DCL) command, with its four mundane, ordinary, commonplace, and monotonous parameters, you’re in for a treat. In V5R4, DCL gets four new parameters and a new data type to boot! CL will never be the same.

    The new DCL provides two new features–the ability to overlay one variable with another and the pointer data type. Let’s look at overlaying data first.

    Overlaid Variables

    Suppose you have a command with a parameter that defines a qualified object name.

         CMD   PROMPT('Do Something')
    
         PARM  KWD(FILE) TYPE(Q1) MIN(1) +
                 PROMPT('Physical file')
    
    Q1:  QUAL  TYPE(*NAME) LEN(10) MIN(1)
         QUAL  TYPE(*NAME) LEN(10) DFT(*CURLIB) +
                 SPCVAL((*CURLIB)) PROMPT('Library')
    

    The command processor passes the qualified object name to the command-processing program as a twenty-byte character string. The first ten characters are the object name. The last ten characters are the library name. Prior to V5R4, you must use the substring function to extract the object and library names to variables of their own.

    pgm (&QualFile)
    
    dcl  &QualFile   type(*char) len(20)
    dcl  &File       type(*char) len(10)
    dcl  &Lib        type(*char) len(10)
    
    chgvar  &File    %sst(&QualFile  1 10)
    chgvar  &Lib     %sst(&QualFile 11 10)
    
    chkobj &Lib/&File *file
    

    The new DCL supports the STG (storage) keyword, which lets you designate how a variable’s storage is to be allocated. STG(*DEFINED) tells the compiler that a variable overlays all or part of another variable. You’ll also need to use the DEFVAR (defined on variable) keyword to tell which variable this new variable is part of. DEFVAR takes two values–the name of the variable that is overlaid and the starting position for the overlay. The default starting position is one.

    Let’s return to the qualified object example. Under V5R4, you may define the object and library variables to be substrings of the parameter. This means that you no longer need the substring operations. Compare the following code to the pre-V5R4 code of the previous example.

    pgm (&QualFile)
    
    dcl  &QualFile   type(*char) len(20)
    dcl  &File       type(*char) len(10) +
                       stg(*defined) defvar(&QualFile 1)
    dcl  &Lib        type(*char) len(10) +
                       stg(*defined) defvar(&QualFile 11)
    
    chkobj &Lib/&File *file
    

    Note that the two sections of code are not equivalent. In the pre-V5R4 version, changing &File or &Lib does not change &QualFile, and changing &QualFile does not change &File and &Lib. In the V5R4 version, changing &File or &Lib does change &QualFile, and changing &QualFile does change &File and &Lib.

    The ability to overlay character fields with character fields is OK, but it doesn’t let us do anything we couldn’t already do using substring and concatenation operations. The real power of this feature is when data of one type is contained within a variable of another type. For example, let’s say you have written a CL command that defines a list of packed decimal parameters.

    CMD  PROMPT('Print Some Customer Report')
    
    PARM KWD(ACCOUNT) TYPE(*DEC) LEN(7 0) MIN(1) +
           MAX(12) PROMPT('Customer account number(s)')
    

    The list of account numbers gets passed to the command-processing program in a fifty-byte string. The first two bytes contain the number of entries in the list in binary format. Each group of four bytes thereafter holds a packed decimal number. Now, how does your CL program extract the decimal values from that big ol’ character string? Not easily. In fact, your CL program can’t do it. It has to call a program in some other language (e.g., RPG or COBOL) to get the job done.

    That little problem goes away with V5R4. Here’s some code from the command-processing program.

    pgm parm(&List)
    
       dcl &List       *char 50
       dcl &NbrOfListE *int   2 stg(*defined) defvar(&List)
    
       dcl &ListNdx    *int   2
       dcl &Offset     *int   2 value(3)
    
       dcl &AccountCh  *char  4
       dcl &AccountNbr *dec   7 stg(*defined) defvar(&AccountCh)
    
       DoFor   &ListNdx from(1) to(&NbrOfListE)
          /* Extract the next account number from the list */
          ChgVar  &AccountCh   %sst(&List &Offset 4)
          /* do something with &AccountNbr here */
          ChgVar  &Offset      (&Offset + 4)
       EndDo
    

    Notice that &AccountCh (a character variable) and &AccountNbr (a decimal variable) are defined to occupy the same spot in memory. This gives me a way to access each customer account number as a decimal value.

    Did you notice the other instance of overlaid data in this example? The first two bytes of the &LIST parameter contain the number of list entries as a binary number. I have defined &NBROFLISTE to contain that number. In V5R3 and earlier, I have to use the %binary (or %bin) built-in function to extract the number of list entries.

    dcl &List        *char  50
    dcl &NbrOfListE  *int    2 
    
    ChgVar  &NbrOfListE    %bin(&List 1 2)
    

    Pointers

    I am not exuberantly excited about the addition of pointers to CL, mainly because I do not like pointers and I avoid them when possible. I first learned how to use pointers eons ago when I learned Pascal. I found them very useful for linked lists and other data structures that I had to master in order to get my computer science degree. While I enjoy programming with pointers for fun, I haven’t found pointers to be of much help in shipping product out the factory doors. (For one use of pointers that I do like, see Define Compile-Time Array Data in D-Specs.)

    However, many C functions and some APIs use pointers. On one hand, if CL is to fully participate as an ILE language, it probably makes sense to add pointer support to CL. On the other hand, CL is fundamentally a job control language. Does a job control language really need to do the sorts of things that RPG, C, COBOL, and Java do so much more easily? For example, V5R4 allows you to bind CL to the C record I/O routines (the ones whose names begin _R), but the code will be messy. Why not use RPG for database processing instead?

    To declare a pointer variable, specify TYPE(*PTR) but do not code the LEN parameter.

    DCL &ListAddr TYPE(*PTR)
    

    Pointer &ListAddr does not point to anything yet. To assign a memory address to it, use the new %ADDRESS built-in function. The following example shows how pointer &ListAddr is set to point to character variable &List1, then later set to point to &List2.

    DCL &List1     TYPE(*CHAR) LEN(57)
    DCL &List2     TYPE(*CHAR) LEN(57)
    DCL &ListAddr  TYPE(*PTR)
    
    CHGVAR   VAR(&ListAddr)  VALUE(%ADDRESS(&LIST1))
    <...more commands...>
    CHGVAR   VAR(&ListAddr)  VALUE(%ADDRESS(&LIST2))
    

    If you like, you may abbreviate %ADDRESS to %ADDR.

    CHGVAR   VAR(&ListAddr)  VALUE(%ADDR(&LIST1))
    

    You can initialize a pointer variable by naming the variable to which it points in the ADDRESS parameter of the DCL command.

    DCL &List      TYPE(*CHAR) LEN(82)
    DCL &ListAddr  TYPE(*PTR)  ADDRESS(&List)
    

    Based Variables

    A based variable does not occupy storage of its own. The pointer on which a based variable is based must be set to the address of another variable before the based variable can be used. Look at the following variable declarations.

    dcl  &ptrText  type(*ptr)
    dcl  &Text     type(*char) len(256) stg(*based) basptr(&ptrText)
    

    Even though &TEXT is defined as a 256-byte character variable, the compiler does not allocate 256 bytes of memory for it. The STG(*BASED) parameter means that the compiler is not to allocate storage. The BASPTR parameter says that the pointer variable &PtrText has the address of the memory that &Text should reference.

    Contrast this to the overlaid variables. In the following example, &Name is assigned to the first 20 bytes of memory occupied by the &Data variable.

    dcl    &Data  *char   80
    dcl    &Name  *char   20  stg(*defined) defvar(&Data)
    

    However, in this code snippet, &Name cannot be used until its basing pointer, &pName, has been assigned a memory address. In this example, &pName is set to the same memory address allocated for the &Data variable, so &Name will be mapped over the first 20 bytes of memory allocated for &Data.

    dcl    &pName  *ptr
    dcl    &Data  *char   80
    dcl    &Name  *char   20  stg(*based) basptr(&pName)
    
    chgvar   var(&pName) value(%addr(&Data))
    

    But what if &Data contains a list of four qualified names, each 20 bytes long? The next code snippet shows how you can change the pointer offset to process each of these names, using based and defined variables. The new %OFFSET (or %OFS) built-in function can be used to store the offset portion of a pointer or, when used on the VAR parameter of a CHGVAR command, to change the offset in a pointer variable.

    dcl    &pName    *ptr
    dcl    &Data     *char   80
    dcl    &Name     *char   20  stg(*based) basptr(&pName)
    dcl    &objname  *char   10  stg(*defined)  defvar(&Name 1)
    dcl    &libname  *char   10  stg(*defined)  defvar(&Name 11)  
    dcl    &loopctr  *int  2
    
    chgvar   var(&pName) value(%addr(&Data))
    
    DoFor &loopctr from(1) to(4)
       If (&libname *eq '*CURLIB') then(callsubr getcurlib)
       
       /* Move the basing pointer for &Name forward 20 bytes */
       Chgvar  var(%offset(&pName))  value(%offset(&pName)+20))
    Enddo   
    

    Maybe another pointer example will help. Suppose there are four identically defined variables that must be processed in the same way. You could duplicate the processing code, once for each variable, but wouldn’t it be nice to write one subroutine that can handle one variable and use the subroutine four times? The following example does just that.

    pgm parm(&Text1 &Text2 &Text3 &Text4)
    
    dcl  &ptrText  type(*ptr)
    dcl  &Text     type(*char) len(256) stg(*based) basptr(&ptrText)
    
    dcl  &Text1    type(*char) len(256)
    dcl  &Text2    type(*char) len(256)
    dcl  &Text3    type(*char) len(256)
    dcl  &Text4    type(*char) len(256)
    
    chgvar  var(&ptrText) value(%addr(&Text1))
    callsubr   Process
    chgvar  var(&ptrText) value(%addr(&Text2))
    callsubr   Process
    chgvar  var(&ptrText) value(%addr(&Text3))
    callsubr   Process
    chgvar  var(&ptrText) value(%addr(&Text4))
    callsubr   Process
    
    subr Process
    
    /* commands that do something with &TEXT */
    if (%sst(&Text 31 1) *eq 'A') do
       <... more commands ...>
    enddo
    chgvar    %sst(&Text 97 6) value('BR-549')
    <... more commands ...>
    
    endsubr
    endpgm
    

    The common subroutine has the name Process. It references the &Text variable. To make Process carry out its mission for any of the procedure’s parameters is a simple matter of setting &ptrText to the address of that parameter. That is, while &ptrText is set to the address of &Text3, all changes to &Text are really changes to &Text3.

    More to Come?

    IBM didn’t add my most-desired feature to V5R4 CL. I was hoping for source code inclusion directives, like /COPY in RPG, COPY in COBOL, and #include in C. Nevertheless, I’m glad to see the compiler enhanced again after so many years. I’ll hope for compiler directives in a future release.

    Share this:

    • Reddit
    • Facebook
    • LinkedIn
    • Twitter
    • Email

    Tags:

    Sponsored by
    Midrange Dynamics North America

    With MDRapid, you can drastically reduce application downtime from hours to minutes. Deploying database changes quickly, even for multi-million and multi-billion record files, MDRapid is easy to integrate into day-to-day operations, allowing change and innovation to be continuous while reducing major business risks.

    Learn more.

    Share this:

    • Reddit
    • Facebook
    • LinkedIn
    • Twitter
    • Email

    Sponsored Links

    Advanced Systems Concepts:  iSeries data access like nothing else with SEQUEL
    COMMON:  Join us at the Spring 2006 conference, March 26-30, in Minneapolis, Minnesota
    Vision Solutions:  The Industry Standard in eServer High Availability

    iSeries Software Helps NetManage’s Recovery Power6 Gets Second Silicon, IBM to Crank the Clock

    Leave a Reply Cancel reply

Volume 6, Number 6 -- February 8, 2006
THIS ISSUE SPONSORED BY:

T.L. Ashford
WorksRight Software
COMMON

Table of Contents

  • V5R4 CL Enhancements, Revealed and Detailed
  • iSeries Security Journal Receiver Management, Part 1
  • Admin Alert: Creating an i5/OS User Profile Architecture

Content archive

  • The Four Hundred
  • Four Hundred Stuff
  • Four Hundred Guru

Recent Posts

  • Meet The Next Gen Of IBMers Helping To Build IBM i
  • Looks Like IBM Is Building A Linux-Like PASE For IBM i After All
  • Will Independent IBM i Clouds Survive PowerVS?
  • Now, IBM Is Jacking Up Hardware Maintenance Prices
  • IBM i PTF Guide, Volume 27, Number 24
  • Big Blue Raises IBM i License Transfer Fees, Other Prices
  • Keep The IBM i Youth Movement Going With More Training, Better Tools
  • Remain Begins Migrating DevOps Tools To VS Code
  • IBM Readies LTO-10 Tape Drives And Libraries
  • IBM i PTF Guide, Volume 27, Number 23

Subscribe

To get news from IT Jungle sent to your inbox every week, subscribe to our newsletter.

Pages

  • About Us
  • Contact
  • Contributors
  • Four Hundred Monitor
  • IBM i PTF Guide
  • Media Kit
  • Subscribe

Search

Copyright © 2025 IT Jungle