• The Four Hundred
  • Subscribe
  • Media Kit
  • Contributors
  • About Us
  • Contact
Menu
  • The Four Hundred
  • Subscribe
  • Media Kit
  • Contributors
  • About Us
  • Contact
  • Guru: Beyond The Basics Of Code Conversion

    March 19, 2018 Paul Tuohy

    Today many RPG programmers are tasked with having to modernize existing programs. These programs often have long and varied history. They may be RPG IV programs or they may be RPG IV programs that were originally RPG II or RPG III programs, or any permutation or combination you can imagine. There are tools available to help us convert from fixed-form RPG to free-form RPG and, within RDi, we have options to help us reformat code and refactor variable names. But these tools can only go so far. We finally reach the point where we have to put fingers to the keyboard and start changing the code. Or do we?

    Within most applications there are recognizable patterns and standards within the code. These patterns and standards may be too specific for a generic conversion tool but there is nothing to stop us from writing our own conversion programs.

    This story contains code, which you can download here.

    In this article, I will describe a conversion program which refactors a set of standard variable names and replaces any CTL-OPT operations with a standard /INCLUDE directive. Although RDi has a marvelous option for refactoring variables names, I would sooner not have to repeat the same process for twenty standard variables names in every program I edit. At the end of the article, I will discuss some other candidates for code conversion programs.

    A Variable Conversion Table

    The following SQL code shows the creation and population of a conversion table of from- and to-values. Our conversion program will scan a source member and replace every occurrence of STRIN with the corresponding value of STROUT. The case of the value of STRIN is not relevant but the case of STROUT is what will be placed into the new source.

    create table cvvars
    (strin  varchar(50) not null default,
     strout varchar(50) not null default);
    
    insert into cvvars
        values ('getRec','getRow')
            ,('dispSc','displayScreen')
            , ('valdSc','validateScreen')
            ,('procSc','processScreen')
            , ('dispCD','displayConfirm')
            ,('F4Prmt','F4Prompt')
            , ('#retrn','returnCode')
            ,('#ctl','g_nextFunction');
    

    A word of caution: the conversion program is going to perform string replacement so be sure to avoid values that may be subsets of longer names, e.g. (from the example above) if a source contains a variable named LastGetRec, it will be changed to LastGetRow.

    Conversion

    The conversion process requires three objects.

    Member Description
    CONV001 SQLRPGLE program to perform the conversion
    LCONV001 Command Prompt for required parameters
    LCONV001C Command Processing Program for LCONV001

    Prompting the LCONV001 command provides options to identify from- and to-members for conversion.

    The conversion program assumes that the code has already been converted to free-form (either with or without **FREE).

    This is the source for the LCONV001 command.

    CMD        PROMPT('Sample Local Conversion')
    /* CRTCMD CMD(LCONV001) PGM(LCONV001C) SRCFILE(CONVPGMS) */
    PARM       KWD(FROMSRCPF) TYPE(QUAL01) PROMPT('From Source File')
    PARM       KWD(FROMMBR) TYPE(*NAME) MIN(1) PROMPT('From Member Name')
    PARM       KWD(TOSRCPF) TYPE(QUAL02) PROMPT('To Source File')
    PARM       KWD(TOMBR) TYPE(*NAME) DFT(*FROMMBR) SPCVAL((*FROMMBR)) +
                          PROMPT('To Member Name')
    
    QUAL01:     QUAL       TYPE(*NAME) MIN(1)
                QUAL       TYPE(*NAME) MIN(1) PROMPT('From Library')
    QUAL02:     QUAL       TYPE(*NAME) DFT(*FROMSRCF) SPCVAL((*FROMSRCF))
                QUAL       TYPE(*NAME) DFT(*FROMLIB) SPCVAL((*FROMLIB)) +
                            PROMPT('To Library')
    

    The command processing CL program (LCONV001C) performs some basic housekeeping before call the conversion program. Refer to the callouts for details.

             PGM (&FROMQUAL &MBR &TOQUAL &TOMBR)
             /* CRTCLPGM PGM(LCONV001C) SRCFILE(CONVPGMS) */
             DCL  &FROMQUAL   *CHAR 20
             DCL  &FROMSRCF   *CHAR 10 STG(*DEFINED) DEFVAR(&FROMQUAL 1)
             DCL  &FROMLIB    *CHAR 10 STG(*DEFINED) DEFVAR(&FROMQUAL 11)
             DCL  &MBR        *CHAR 10
             DCL  &TOQUAL     *CHAR 20
             DCL  &TOSRCF     *CHAR 10 STG(*DEFINED) DEFVAR(&TOQUAL 1)
             DCL  &TOLIB      *CHAR 10 STG(*DEFINED) DEFVAR(&TOQUAL 11)
             DCL  &TOMBR      *CHAR 10
             DCL  &SRCTYPE    *CHAR 10
             DCL  &TEXT       *CHAR 50
    
    (A)      IF (&TOLIB = '*FROMLIB') CHGVAR &TOLIB &FROMLIB
             IF (&TOSRCF = '*FROMSRCF') CHGVAR &TOSRCF &FROMSRCF
             IF (&TOMBR = '*FROMMBR') CHGVAR &TOMBR &MBR
    
    (B)      CHKOBJ OBJ(&TOLIB/&TOSRCF) OBJTYPE(*FILE) MBR(&TOMBR)
             MonMsg (CPF9815) Exec(DO)
                RTVMBRD    FILE(&FROMLIB/&FROMSRCF) MBR(&MBR) +
                          SRCTYPE(&SRCTYPE) TEXT(&TEXT)
                ADDPFM     FILE(&TOLIB/&TOSRCF) MBR(&TOMBR) SRCTYPE(&SRCTYPE) +
                             TEXT(&TEXT))
             ENDDO
    
    (C)      CALL CONV001 (&FROMLIB &FROMSRCF &MBR &TOLIB &TOSRCF &TOMBR)
    

    (A) Set the values of the TO parameters if default values were specified.

    (B) If the TO member does not exist, retrieve the source type and text of the FROM member and create the TO member.

    (C) Call the conversion program.

    The SQLRPGLE Conversion Program

    The SQLRPGLE Conversion Program (CONV001) converts source from the FROM library/source file/member to the TO library/source file/member.

    **free
    // CRTSQLRPGI OBJ(CONV001) SRCFILE(CONVPGMS) RPGPPOPT(*LVL2) DBGVIEW(*SOURCE)
    ctl-opt dftActGrp(*no) actGrp(*new) datFmt(*ymd);
    
    dcl-pi *n;
       libraryIn     char(10);
       sourceFileIn  char(10);
       memberIn      char(10);
       libraryOut    char(10);
       sourceFileOut char(10);
       memberOut     char(10);
    end-pi; 
    

    Templates

    Templates define the format of a source line and the variables conversion table defined earlier.

    dcl-ds t_sourceLine template qualified inz;
       srcSeq  zoned(6: 2);
       srcDate zoned(6: 0);
       srcData char(100);
    end-ds;
    
    dcl-ds t_cvtNames qualified template;
       strIn  varchar(50);
       strOut varChar(50);
    end-ds; 
    

    Data Structure Arrays

    Data structures arrays (based on the templates) are defined for the source member input, the source member output and the list of variable names for conversion. The integers are used to indicate the number of rows in the corresponding arrays.

    dcl-dS g_inRec       likeDs(t_sourceLine)
                         inz(*likeDs)
                         dim(32766);
    dcl-dS g_outRec      likeDs(t_sourceLine)
                         inz(*likeDs)
                         dim(32766);
    dcl-s  g_numLines    int(10);
    dcl-s  g_currentLine int(10);
    
    dcl-ds g_cvtNames likeDs(t_cvtNames) dim(1000);
    dcl-s  g_numNames int(10); 
    

    If the source member contains more than 32,766 lines of code, I recommend some prior surgery – or the logic to the conversion program needs to change to “page” the source.

    Other Work Fields

    Work fields are used to process the current line (in original format and in upper case) and a loop counter.

    dcl-s g_wrkLine   varChar(100);
    dcl-s g_original  varChar(100);
    dcl-s i           int(10); 
    

    Mainline

    The mainline processes the input source member, converts the source, and outputs the new member. Refer to the callouts for details.

        exec SQL
           set option commit=*none, naming=*SYS;
    
    (A) g_numLines = get_Source(g_inRec);
    (B) g_numNames = get_Names(g_cvtNames);
    
    (C) set_CTLOPT();
    
    (D) for i = 1 to g_numLines;
    (E)    g_original = %trimR(g_inrec(i).srcData);
           g_wrkLine = str_toUpper(g_original);
    
           g_outRec(g_currentLine).srcDate = g_inrec(i).srcDate;
    
    (F)    if check_CTLOPTorFREE();       // Ignore current CTL-OPT or **FREE
    
           else;                          // or
    (G)       addNames();                 // Scan/replace names
           endIf;
    
        endFor;
    
    (H) put_Source();
    
        *inLR = *on; 
    

    (A) Retrieve the source member into the G_INREC data structure array. G_NUMLINES contains the number of source lines retrieved.

    (B) Retrieve the list of variables names for conversion into the G_CVTNAMES data structure array. G_NUMNAMES contains the number of variables names for conversion.

    (C) Add the /INCLUDE directive for the standard CTL-OPT copy member.

    (D) Process each source line.

    (E) Copy the current source lines to work fields – in original form and in uppercase.

    (F) Ignore any lines containing CTL-OPT or **FREE.

    (G) Replace any required variables names.

    (H) Output the new source member.

    Convert To Uppercase

    The str_toUpper() subprocedure uses the SQL UPPER scalar function to convert a request string to uppercase.

        dcl-proc str_toUpper;
           dcl-pi *n     varchar(200);
              stringIn   varChar(200) const;
           end-pi;
    
           dcl-s stringOut varChar(200);
    
           exec SQL
              values upper(:stringIn) into :stringOut;
    
           return %trimR(stringOut);
        end-proc; 
    

    Adding A Standard CTL-OPT

    The set_CTLOPT() subprocedures adds an /INCLUDE directive for a standard CTL-OPT copy member. The subprocedure caters for the first line containing a **FREE directive. The include directive will be added as the first or second line depending on whether or not there is a **FREE directive.

        dcl-proc set_CTLOPT;
           dcl-pi *n end-pi;
    
           dcl-s j  int(5) inz(1);
           dcl-s S_INC_HEADER  varChar(50)  inz('      /include QCopy,stdHeader') ;
    
           if (%trimR(str_toUpper(g_inrec(1).srcData)) = '**FREE');
              eval-Corr g_outRec(1) = g_inrec(1);
              j += 1;
              S_INC_HEADER = %trim(S_INC_HEADER);
           endIf;
    
           g_outRec(j).srcDate = uDate;
           g_outRec(j).srcData = S_INC_HEADER;
           g_currentLine = j + 1;
    
         end-proc;
    

    Get The Input Source

    The get_Source() subprocedure retrieves the input source member into a data structure array. Refer to the callouts for details.

        dcl-proc get_Source;
           dcl-pi *n int(10);
              list  likeDs(g_inRec) dim(32600);
           end-pi;
    
           dcl-s gotRows int(10);
           dcl-s getRows int(10) inz(%elem(list));
           dcl-s mySQL   varChar(1000);
    
    (A)    mySQL = 'create or replace alias qtemp.sourceIn for ' +
                   %trim(libraryIn) + '.' + %trim(sourceFileIn) +
                   '(' + %trim(memberIn) + ')';
    
           exec SQL
    (A)      prepare AliasSourceIn from :mySQL;
    
           if (SQLCode <> 0);
              return 0;
           endIf;
    
           exec SQL
    (A)       execute AliasSourceIn;
    
    (B)    exec SQL
              declare get_Source scroll cursor for
                 select srcSeq, srcDat, srcDta
                 from   qtemp.sourceIn
                 order by srcSeq
                 for read only;
    
           exec SQL
              open get_Source;
    
           exec SQL
    (C)       fetch first from get_Source for :getRows rows into :list;
    
    (D)    gotRows = SQLERRD(3);
    
           exec SQL
              close get_Source;
    
           return gotRows;
    
        end-proc;
    

    (A) Use dynamic SQL to create an Alias (SOURCEIN in QTEMP) which aliases the requested member to be processed. Think of this as an OVRDBF.

    (B) Declare a cursor to input source rows from the alias (which is now aliasing the requested member).

    (C) Use a multi row fetch to retrieve all source into the data structure array.

    (D) Store the number of rows retrieved.

    Get The List Of Variable Names

    The get_Names() subprocedure retrieves the list of variables names (for conversion) into a data structure array. The subprocedure uses a standard multi-row fetch. Note that the select statement converts the value of the STRIN variable to uppercase.

        dcl-proc get_Names;
           dcl-pi *n int(10);
              list  likeDs(t_cvtNames) dim(1000);
           end-pi;
    
           dcl-s gotRows int(10);
           dcl-s getRows int(10) inz(%elem(list));
    
           exec SQL
              declare get_Names scroll cursor for
                 select upper(strIn), strout
                 from   cvvars
                 for read only;
    
           exec SQL
              open get_Names;
    
           exec SQL
              fetch first from get_Names for :getRows rows into :list;
    
           gotRows = SQLERRD(3);
    
           exec SQL
              close get_Names;
    
           return gotRows;
    
        end-proc;
    

    Ignore Lines

    The check_CTLOPTorFREE() subprocedure returns a true condition if the current line contains a CTL-OPT or *FREE directive. This means that this line will be ignored and will not be copied to the output source.

        dcl-proc check_CTLOPTorFREE;
           dcl-pi *n ind end-pi;
    
           return ((%scan('CTL-OPT':g_wrkLine) > 0) or
                   (g_wrkLine = '**FREE') );
        end-proc;
    

    Replace Variable Names

    The add_Names() subprocedure scans the source line for all occurrences of variables to be replaced and replaces them. Refer to the callouts for details.

        dcl-proc addNames;
           dcl-pi *n end-pi;
    
           dcl-s i   int(10);
           dcl-s j   int(10);
    
    (A)    for i = 1 to g_numNames;
    (B)       doU j = 0;
    (C)          j = %scan(g_cvtNames(i).strIn: g_wrkLine);
                 if j > 0;
    (D)             g_original = %trimR(%replace(g_cvtNames(i).strOut:
                                                 g_original:
                                                 j :
                                                 %len(g_cvtNames(i).strIn)));
    (D)             g_wrkLine = str_toUpper(g_original);
                 endIf;
              endDo;
           endFor;
    
    (E)    g_outRec(g_currentLine).srcData = g_original;
    (F)    g_currentLine += 1;
    
        end-proc;
    

    (A) Check for each variable in the conversion table.

    (B) Keep scanning a line for a variable until it isn’t found. A variable may be defined more than once in a line.

    (C) Scan the line for the input variable.

    (D) Replace the variables name with the new name.

    (E) When all variables have been processed, set the new source line.

    (F) Set the counter for the next source line.

    Output Source Member

    The put_Source() subprocedure uses a multi-row insert to create the new source member. Refer to the callouts for details.

        dcl-proc put_Source;
           dcl-pi *n end-pi;
    
           dcl-s i  int(10);
    
           dcl-s mySQL   varChar(1000);
    
    (A)    mySQL = 'create or replace alias qtemp.sourceOut for ' +
                   %trim(libraryOut) + '.' + %trim(sourceFileOut) +
                   '(' + %trim(memberOut) + ')';
    
           exec SQL
    (A)      prepare AliasSourceOut from :mySQL;
    
           if (SQLCode <> 0);
              return;
           endIf;
    
           exec SQL
    (A)       execute AliasSourceOut;
    
    (B)    for i = 1 to g_currentLine;
              g_outRec(i).srcSeq = i;
           endFor;
    
           exec SQL
    (C)      delete from qtemp.sourceOut ;
    
           g_currentLine -= 1;
           exec SQL
    (D)      insert into qtemp.sourceOut
               :g_currentLine rows
               values (:g_outRec)
               with nc;
        end-proc; 
    

    (A) Use dynamic SQL to create an Alias (SOURCEOUT in QTEMP) which aliases the member to be created. As with the input source member, this is a form of OVRDBF.

    (B) Loop through the elements of the array and reset the source sequence number.

    (C) Delete any existing lines from the source member.

    (D) Insert the new source lines.

    Other Candidates For Conversion

    Hopefully, this conversion program has given you the idea of how a code conversion program might work for you. What can be achieved by a conversion is really dependent on the patterns and standards in your code. And conversion is not just for single lines of code, as in this example.

    You could have a conversion program that replaces blocks of code. For example, you currently have a process that copies data into standard work fields and executes a subroutine and you want to change this to a subprocedure call. Delimit the block of code to be converted with special comments (e.g. //$$BmakeProc and //$$EmakeProc) and your conversion program processes and converts all of the lines between the comments.

    Or, if you make use of “standard” RPG indicators, how about renaming them to named indicators?

    You know the patterns in your code. Can you write a program to change them?

    Share this:

    • Reddit
    • Facebook
    • LinkedIn
    • Twitter
    • Email

    Tags: Tags: FHG, Four Hundred Guru, Guru, IBM i, RDi, RPG, RPG II, RPG III, RPG IV

    Sponsored by
    Raz-Lee Security

    Protect Your IBM i and/or AIX Servers with a Free Virus Scan

    Cyber threats are a reality for every platform, including IBM i and AIX servers. No system is immune, and the best defense is prompt detection and removal of viruses to prevent costly damage. Regulatory standards across industries mandate antivirus protection – ensure your systems are compliant and secure.

    Get My Free Virus Scan

    Share this:

    • Reddit
    • Facebook
    • LinkedIn
    • Twitter
    • Email

    Ensono Embiggens With Wipro Data Center Deal The IBM i Roadmap Leads To A Familiar Place

    Leave a Reply Cancel reply

TFH Volume: 28 Issue: 21

This Issue Sponsored By

  • Fresche Solutions
  • New Generation Software
  • Software Concepts
  • WorksRight Software
  • COMMON

Table of Contents

  • Bang For The Buck On Power9 Entry Hardware
  • The IBM i Roadmap Leads To A Familiar Place
  • Guru: Beyond The Basics Of Code Conversion
  • Ensono Embiggens With Wipro Data Center Deal
  • Sundry Power Systems Announcements, Here And There

Content archive

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

Recent Posts

  • POWERUp 2025 –Your Source For IBM i 7.6 Information
  • Maxava Consulting Services Does More Than HA/DR Project Management – A Lot More
  • Guru: Creating An SQL Stored Procedure That Returns A Result Set
  • As I See It: At Any Cost
  • IBM i PTF Guide, Volume 27, Number 19
  • IBM Unveils Manzan, A New Open Source Event Monitor For IBM i
  • Say Goodbye To Downtime: Update Your Database Without Taking Your Business Offline
  • i-Rays Brings Observability To IBM i Performance Problems
  • Another Non-TR “Technology Refresh” Happens With IBM i TR6
  • IBM i PTF Guide, Volume 27, Number 18

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