IBM Support

Coding in RPG IV - a beginner's tutorial

General Page

You are in: RPG Cafe > Tutorial on RPG

Short URL: https://ibm.biz/rpgCafe_Freeform_Rpg_Tutorial

RPG Cafe: Tutorial on RPG

Coding in Free-Form RPG IV

This tutorial is intended for experienced programmers who want to learn RPG IV (also known as ILE RPG).

This tutorial assumes.

  • That you have IBM i 7.1 or later so that you can use the free-form version of most RPG IV statement types. (There may be some coding examples that require later releases. If so, this will be noted with the example. But 7.1 will be sufficient for most of this tutorial.)
  • That you have no prior knowledge of RPG III.
  • That you have some prior knowledge of IBM i (also known as AS/400, iSeries, System i).
  • That you know where to find the manuals for RPG in the IBM Knowledge Center.

The coding examples are intended to show the feature being discussed, but they sometimes have a "Bonus features for this example" section following the example that lists additional interesting things to note about the example, usually things that have not been discussed yet. Readers can choose to ignore those "Bonus features" sections without worrying about missing something, since the material is covered in later chapters.

Chapter 1: Hello world

This chapter takes you through the steps to create and run an RPG IV program.

It assumes you are using one of two ways to do your coding:

  • RDi
  • PDM and SEU through some type of 5250 emulator

Initial setup

If you are using RDi:

  • Create a connection to your IBM i.
  • Create a library. Remember the name of the library you create. You need it later when you call your first program. (Replace yourlibrary in the instructions with the name of the library you created.)
  • Create a source file.
  • Create a source member in the file called HELLO.RPGLE.
  • Open the member in the editor.

If you are using SEU:

  • Log on to a session on your IBM i.
  • Create a library by using the CRTLIB command.
  • Create a source file by using the CRTSRCPF command.
  • WRKMBRPDM specifying your library and source file.
  • Create a source member HELLO in the file by using F6 in WRKMBRPDM or by using the ADDPFM command, giving it type RPGLE.
  • Open the member in SEU by using the "2" option.
  • Warning: SEU gives errors for every free-form H, F, D, or P statement. The RPG syntax checker used by SEU was last updated in 6.1, so it does not understand the new syntax. If you still want to use SEU, you have to exit with errors after you save your source.

Entering the RPG program statements in "fully-free" form

  • Type the following code. Be sure that the first **FREE goes in column 1 of line 1. If you are using RDi, the editor shows you all the columns. If you are using SEU, the editor does not show you columns 1-5 by default, so the first column you see is column 6. Use F19 to shift the code left so you can see column 1.
    **free
    dsply 'Hello World';
    return;
  • Compile the program
    • If you are using RDi, you can compile from the menu within the editor, or you can save your changes and compile by right-clicking on the member in the navigation. Either way, select the CRTBNDRPG command, and take all the defaults.
    • If you are using SEU, exit the editor by using F3, and compile by using option 14. You could also compile from the command line by using the CRTBNDRPG command, and by using F4 to prompt the command.
  • If you are using RDi, log on to a session on the IBM i by using an emulator
    • Do the following command.

      ===> WRKOBJPDM yourlibrary

      (The WRKOBJPDM command puts you into a list of the objects in that library. You don't actually need to do this step to call your program, but because of an oddity in how the system determines whether to halt your job to show you a simple DSPLY message, it's convenient to get into a screen where the job halts. If you decide not to use WRKOBJPDM, or if it's not available, then your program just ends without letting you see the DSPLY. In that case, use DSPJOBLOG, then F10, then F6, to see the output.)

    • If you are using SEU, exit the editor by using F3, and compile by using option 14. You could also compile from the command line by using the CRTBNDRPG command, and by using F4 to prompt the command.
  • (If you are using PDM, you are already logged on.)
  • Use the CALL command to call the HELLO program, substituting the name of your library for yourlibrary.

    ===> CALL yourlibrary/HELLO

  • It puts up a message saying "DSPLY Hello World"
  • Just press ENTER to end the program.

"Fully free form" vs "column-limited free form"

RPG has two modes of free-form code. The historical mode (column-limited mode) requires that all free-form code must be coded between columns 8 and 80. Fully-free mode allows free-form code in any column, with no limit on line-length.

Fully free-form source must have **FREE in column 1 of line 1. All the code must be free-form.

Non-fully-free source, or column-limited source does not have **FREE in line 1. You can mix fixed-form code (code that uses columns 6 and 7) and free-form code in column-limited source.

This tutorial assumes you have **FREE at the beginning of your source, but it also works if you use column-limited source, if you ensure that columns 1-7 are blank and that you don't go past column 80. The examples have 7 blanks at the beginning to make it easy to copy and paste into column-limited source, but if you have **FREE at the beginning of your source, you can remove those blanks if you like.

Dealing with compiler errors

  • Edit your source again, and change it so instead of displaying 'Hello World', it tries to display a variable called "name". This is an undefined variable and generates a compiler error.
           dsply name;
           return;
  • Delete the old version of the program by using the DLTPGM command.
  • Compile the program again. It fails with a severity 30 error.
  • If you are using RDi, click the RNF7030 error in the Error List Window. It highlights the line that tried to display the "name" variable.
  • If you are using SEU, use the WRKSPLF command and then use F18 to get to the end of the list of spooled files. Use option 5 on the HELLO spooled file and use the B command to go to the end of the listing. Page up a bit until you find the list of the error messages, then take note of the error message with the highest severity (the RNF7030 message). Return to the beginning of the file and enter RNF7030 on the search line, then hit F16 to locate the error message in the listing. The error message has a statement number (statement 2). Page back in the listing to find statement 2 (on the left side of the listing). It is the line that tried to display the "name" variable.
  • The error message indicates that NAME is not defined. To correct it, you have to add a definition for a variable called NAME. (Note that RPG IV is not case-sensitive for variable names, so variable "name" is the same as variable "NAME". The uppercase form of the name appears in the listing in error messages and the cross-reference.)
  • Add the following dcl-s statement to the beginning of your program to define variable "name". Remember to code only between columns 8 - 80.
           dcl-s name char(10) inz('Jim');
    
           dsply name;
           return;
  • Recompile the program and call it again. It puts up a message saying "DSPLY Jim".

Debugging

Compile your program again, specifying a debug view other than *NONE or *STMT. For example, specify DBGVIEW(*LIST). (But *ALL, *SOURCE, or *COPY would all work fine here.)

Now, start the debugger. For now, I'll just mention the system debugger, but there is a more intuitive debugger associated with RDi that you can investigate separately.

Assuming your program is called QTEMP/MYPGM, do the following commands:

===> STRDBG QTEMP/MYPGM
   - at this point, you can just use F10 to exit the screen
   - when you call the program, it stops on the first
     executable statement
   - or you could also use F6 to set a breakpoint on a
     particular statement
===> CALL QTEMP/MYPGM
   - when you get a breakpoint, step (F10) through the program
   - display variables (F11) along the way
===> ENDDBG

That's it!

From now on, I'll assume you know

  • How to edit and compile your code.
  • How to locate the error messages associated with a failed compile.
  • How to call a program.
  • How to debug a program.

Chapter 2: General info about RPG

This chapter introduces you to general information about RPG source code.

RPG source code

For free-form statements, the RPG compiler uses only columns 8 - 80 of a source member. Columns 1 -5 and columns 81+ are used for comments. Columns 7 and 8 are only used for fixed form statements. The only fixed-form statements you need to use are I and O statements, and they are rarely used in RPG nowadays.

There are several types of RPG statements.

  1. Control statements, also called Header statements, also called "H specs" due to the historical fixed-form statements that start with H in column 6. These contain general-purpose keywords that affect the entire module.
  2. File and Definition statements, also called "F specs" and "D specs". File statements define the files to be used in the module. Definition statements define constants, variables, and prototypes.
  3. Input specifications, also called "I specs". These define the input record layouts for your files. The RPG compiler generates I specs for externally-described input-capable files.
  4. Calculation statements, also called "C specs". Here is where you code the logic of your procedures.
  5. Output specifications, also called "O specs". These define the output record layouts for your files. The RPG compiler generates O specs for externally-described output-capable files.
  6. Procedure statements, also called "P specs". These start and end subprocedures.

The specifications must appear in the order given above, but it is not necessary to code all the specifications. A module could contain just a single C spec.

There are fixed-form versions of all the statements, but the only ones you might need to use are I and O statements, or a few historical types of File or Calculation statements. This tutorial does not cover fixed-form code. You can see many examples in the ILE RPG Reference.

RPG cycle

RPG was originally created to handle files. The intention was that the program would read a record from the "primary file" and for each record, it would perform the calculations. When it reached the last record, it would close the file and end the program.

To artificially create the "last record" situation, the program could set on the "Last Record" indicator, called *INLR.

For programs that do not have a primary file, it is still necessary to stop the program from looping through the calculations. This can be done two ways:

  1. By using the RETURN operation.
  2. By setting on the Last Record indicator.
           *inlr = '1';

There are subtle differences in these two mechanisms that are discussed in later chapters.

Chapter 3: Variables and procedures

This chapter introduces you to defining constants, variables and prototypes.

Defining constants, variables and prototypes

The Definition statement, often called the "D spec" is used to define constants, variables, prototypes, and "procedure interfaces". The statement begins with DCL-x where x is C, S, DS, PR, or PI, depending on the type of item being defined. This is followed by the name, and then one or more keywords, then a semicolon.

Most of the keywords can go in any order you like, but if you have a data-type keyword such as CHAR or PACKED, it must be the first keyword.

Define a constant

The constant is the simplest RPG definition. It just has one keyword, CONST, and it's optional to actually specify the keyword. 100 and CONST(100) mean exactly the same thing for a constant definition.

Paste the following code into a source member, then compile and run it.

       dcl-c MAX_ELEMS 100;
       dcl-c default_city_name 'London';

       dsply max_elems;
       dsply default_city_name;
       return;

Define a standalone field

TThe standalone field is another simple RPG definition. A standalone field is an ordinary scalar variable. It has a name, a definition type of 'S', and a type.

Paste the following code into a source member, then compile and run it.

       dcl-s num int(10);

       for num = 1 to 3;
          dsply ('i = ' + %char(num));
       endfor;
       return;

The definition statement defines a field named "num". The INT keyword indicates that the field is an integer. The "10" indicates that it has 10 digits; this is a 4-byte integer. The "s" in DCL-S indicates that it is a standalone field.

Bonus features in this example:

  • The "for loop".
  • The %char built-in function, which converts the numeric value to a readable character form such as "-12.345".
  • The '+' operator acts on strings as well as numeric values.

Exercise 3-1

  1. Define a standalone field of type character with length 5. The data type keyword for character is CHAR.
  2. Code an assignment statement to set the character field to 'hello'.
  3. Display the character field.

Solution

Define a data structure

A data structure is defined with one statement the data structure itself, using DCL-DS. This is followed by one statement for each subfield. Subfield statements just start with the name of the subfield. The data structure usually ends with an END-DS statement. If you are defining the data structure like another data structure by using the LIKEDS keyword, or like a record format by using the LIKEREC keyword, then you don't code END-DS.

Paste the following code into a source member, then compile and run it.

Note: This example uses the INZ keyword, which provides an initialization for the subfield. You can also code the INZ keyword for a standalone field.

       dcl-ds info qualified;
           name char(10) inz('Sam');
           salary packed(9 : 2) inz(50000.25);
       end-ds;
       dcl-ds otherInfo likeds(info) inz(*likeds);

       dsply (info.name
            + ' has a salary of'
            + %char(info.salary));
       otherInfo.name = 'Joe';
       otherInfo.salary += 10000;
       dsply (otherInfo.name
            + ' has a salary of'
            + %char(otherInfo.salary));
       return;

Bonus features in this example:

  • The QUALIFIED keyword, which means that the subfields of the data structure must be qualified by the data structure name, DS.SUBFIELD. Without the QUALIFIED keyword, subfields are referred to just by their name.
  • The LIKEDS keyword is used to define another data structure with the same subfields as the parent data structure. The LIKEDS data structure is automatically qualified.
  • The INZ(*LIKEDS) keyword is used to initialize the LIKEDS data structure the same as the parent.
  • The += operator works the same as it does in C and Java™. It adds the value on the right-hand-side to the variable on the left-hand side.

Define an array

You can define an array of scalars or an array of data structures.

RPG supports only one dimension for arrays. Multiple-dimension arrays can be simulated by using data structure arrays with array subfields; instead of coding cell(i j k) you would code table(i).row(j).col(k).

The dimension of the array is specified by using the DIM keyword.

Array indexes are specified with parentheses.

Define a scalar array

Paste the following code into a source member, then compile and run it.

       dcl-s dates date(*iso) dim(3);

       dates(1) = %date();             // the current date
       dates(2) = %date() + %days(1);  // tomorrow
       dates(3) = %date() + %years(1); // next year
       dsply (%char(dates(1)) + ' '
            + %char(dates(2)) + ' '
            + %char(dates(3)));
       return;

Bonus features in this example:

  • The date data type with the ISO format (yyyy-mm-dd).
  • The %date built-in function, which returns the current date when no parameter is specified. %date can also convert a character or numeric parameter to a "true date".

Define a data structure array

Note: This example requires a 7.2 compiler, and you might need a PTF. See http://ibm.biz/spring_2017_rpg_enhancements for PTF details.

Paste the following code into a source member, then compile and run it.

       dcl-ds families qualified dim(5);
          address varchar(50);
          numPeople uns(3);
          dcl-ds people dim(8);
             name varchar(25);
             age packed(5);
          end-ds;
       end-ds;
       dcl-s numFamilies uns(5) inz(0);
       dcl-s i int(10);
       dcl-s j int(10);

       families(1).address = '10 Mockingbird Lane';
       families(1).people(1).name = 'Alice';
       families(1).people(1).age = 3;
       families(1).people(2).name = 'Bill';
       families(1).people(2).age = 15;
       families(1).numPeople = 2;
       numFamilies = 1;

       for i = 1 to numFamilies;
          dsply (families(i).address);
          for j = 1 to families(i).numPeople;
          dsply (families(i).people(j).name
               + ' is '
               + %char(families(i).people(j).age)
               + ' years old.');
          endfor;
       endfor;
       return;

Bonus features in this example:

  • The "name" and "address" subfields are defined as type VARCHAR. This means that the string variable has varying-length; the length given in the definition is the maximum length for the variable. The storage for a varying-length string variable is prefixed by a 2 or 4 byte value that holds the current length of the data part of the variable. In the example, the assignment of "Alice" to the name subfield would set the current length to 5.
  • The nested "people" subfield of the "families" data structure is defined with the DCL-DS keyword, so it is both a subfield and a data structure.
  • The unsigned 5 and unsigned 3 data types. UNS(5) defines a 2-byte unsigned integer that can hold up to 5 digits. UNS(3) defines a 1-byte unsigned integer that can hold up to 3 digits.

Exercise 3-2

  1. Change the data type for NAME to CHAR instead of VARCHAR.
  2. Recompile and run the program.
  3. Explain why the output doesn't look the same as before.

Solution

Define a prototype

RPG prototypes describe how to call a program, procedure, or Java method.

The definition statement starts with DCL-PR. Similar to data stuctures, the DCL-PR statement is followed by parameter definitions, and then the prototype is ended with an END-PR statement. You always need the END-PR statement for a prototype.

The EXTPROC or EXTPGM keyword indicates whether it is calling a procedure or program and it also indicates exactly which procedure or program to call. (Calls to Java methods also use the EXTPROC keyword.)

If neither the EXTPROC nor EXTPGM keyword is coded, the EXTPROC keyword is assumed.

Example 1: Call a program

A common program to call is QCMDEXC. This program runs a system command.

       dcl-pr qcmdexc extpgm('QCMDEXC');
          theCmd char(3000) const;
          cmdLen packed(15 : 5) const;
          dbcs char(3) const options(*nopass);
       end-pr;
       dcl-s cmd varchar(100);

       cmd = 'DSPJOB OUTPUT(*PRINT)';
       qcmdexc (cmd : %len(cmd));
       qcmdexc ('WRKSPLF' : 7);
       return;

Bonus features in this example:

  • The program name in the EXTPGM keyword is case-sensitive. The system would not be able to find the program if the RPG program specified 'QcmdExc' in the EXTPGM keyword.
  • The CONST keyword indicates that the called program does not modify the parameter. When CONST is specified, the passed parameter does not have to exactly match the type and length on the prototype. If the type and length don't match, the RPG compiler creates a temporary variable of the required type and pass that temporary to the called program. Coding CONST also allows literals and expressions to be passed as parameters.
  • The call that uses the prototype is coded with the parameters in parentheses. The parameter separator is a colon, not a comma, as is more usual in other languages.
  • The %len built-in function returns the current length of the varying-length variable "cmd".

Note: If the RPG prototype name is the same as the actual program name, you can just code EXTPGM with no parameter. The RPG compiler uppercases the prototype name to determine the actual program name. Here is an alternate version of the QCMDEXC prototype.

       dcl-pr qcmdexc extpgm;
          theCmd char(3000) const;
          cmdLen packed(15 : 5) const;
          dbcs char(3) const options(*nopass);
       end-pr;

Example 2: Call a procedure

For this example, we call the C runtime printf() function to print a message to the standard output instead of to the external message queue.

       /if defined(*CRTBNDRPG)
         ctl-opt dftactgrp(*no)
                 actgrp(*new);
       /endif
       ctl-opt option(*srcstmt);

       dcl-s num int(10) inz(25);

       print ('This message is much longer than the 52 '
            + 'characters that DSPLY allows. '
            + 'The value of variable "num" is ' + %char(num));
       return;

       dcl-proc print;
          dcl-pi  *n;
             msg varchar(5000) const;
          end-pi;
          dcl-pr printf extproc(*dclcase);
             template pointer value options(*string);
             dummy int(10) value options(*nopass);
          end-pr;
          dcl-c NEWLINE x'15';

          printf(msg + NEWLINE);
       end-proc print;

Bonus features in this example:

  • Instead of calling printf() directly, this example has a subprocedure called print() that handles the call to printf(). Calling printf() directly is a bit awkward because it has to add the new-line character, so it's convenient to wrap it in our own procedure.
  • The print() procedure defines a constant NEWLINE with the hexadecimal value x'15'.
  • The EXTPROC keyword uses *DCLCASE. This means that the external procedure is "printf", the same as the RPG prototype, in the same case as the RPG prototype. If we wanted to, we could code EXTPROC('printf'); we would have to do this if we wanted to use some other name for the RPG prototype, such as print_to_stdout.
  • The prototype for printf() has an extra "dummy" parameter. This is required because the C prototype for printf() indicates that it takes a variable number of parameters. The RPG way to indicate that a procedure takes a variable number of parameters is to use the OPTIONS(*NOPASS) keyword. OPTIONS(*NOPASS) indicates that it is not necessary to pass that parameter.
  • The "template" parameter for printf() is defined as a pointer (the POINTER data type keyword). The parameter is passed by value (the VALUE keyword). The parameter is defined with the OPTIONS(*STRING) keyword, which allows you to pass a character string as the parameter. When a character string is coded as the parameter, the RPG compiler creates a temporary variable with a "null-terminated" version of the parameter. printf() assumes that the first parameter is null-terminated; the null-terminator is used by printf() to determine the length of the first parameter.
  • Conditional compile directives for the first control statement (CTL-OPT statement), /IF and /ENDIF. The DFTACTGRP and ACTGRP keywords are only allowed with CRTBNDRPG, these directives control whether those keywords are seen by the compiler. If the CRTBNDRPG command is used, "*CRTBNDRPG" is defined, and the two H specs between the /IF and the /ENDIF is used in the compile. If the CRTRPGMOD command is used, those two lines are not included in the compile.
  • The control statements have three keywords.
    1. DFTACTGRP(*NO): This keyword is required if the program makes bound calls.
    2. ACTGRP(*NEW): This keyword sets the activation group for the program.
    3. OPTION(*SRCSTMT). This keyword causes the compile listing to have the same statement numbers as the source file. Most RPG programmers use this keyword.

Chapter 4: Introduction to files

This chapter introduces you to using files.

The file statement

The File statement is used to define a file to be used in the RPG module. File statements start with DCL-F (declare file). Like definition statements, the file statement starts with the name to be used for the file in the RPG module. The name is followed by keywords, then a semicolon. If you code a device keyword to say what type of file it is (DISK, PRINTER, WORKSTN), then the device keyword must be the first keyword.

A simple example

Let's start with a little example where we just read all the records of a file.

First, let's get a file to read. Enter the following command on the command line. The command produces a file MYLIB/RPGTESTF that lists the *FILE objects in QGPL whose names start with QRPG. (For this example, change "MYLIB" to the name of your own library, in both the DSPOBJD command and the RPG program)

===> DSPOBJD OBJ(QGPL/QRPG*) OBJTYPE(*FILE) OUTPUT(*OUTFILE) OUTFILE(MYLIB/RPGTESTF)

Now, compile and run the following RPG program.

  • Specify DBGVIEW(*ALL) or DBGVIEW(*LIST) on the compile command so you can get a listing view.

When you run the program, just press ENTER on each DSPLY that shows up.

       dcl-f rpgtestf usropn extdesc('MYLIB/RPGTESTF') extfile(*extdesc);

       open rpgtestf;
       read rpgtestf;
       dow not %eof;
          dsply ODOBNM;
          read rpgtestf;
       enddo;
       close rpgtestf;
       return;

If MYLIB is not in your library list at compile time and runtime, change your DCL-F command to the following, adding the EXTDESC and EXTFILE keywords so that the system can find the file. Add those keywords to all the examples throughout this chapter that use file RPGTESTF.

       dcl-f rpgtestf usropn extdesc('MYLIB/RPGTESTF') extfile(*extdesc);

If you haven't seen the power of RPG before, you might be wondering where ODOBNM comes from.

Try running it under debug by using the listing view. (If you forgot to compile with DBGVIEW(*ALL) or DBGVIEW(*LIST), compile it again.)

If you compile with DBGVIEW(*ALL), you have to choose the listing view while you are debugging.

  • To use the listing view with RDI, right click in the debug window, click Show View and select "Show *LISTING".
  • To use the listing view with STRDBG, hit F15 and select "ILE RPG Listing View".

When you first see the debug listing view, it looks very different from your original code. You see several RPG statements that were generated by the RPG compiler. These are "Input specifications", and they describe the input buffer of the RPGTESTF file. There is one I spec for each field in the file.

When you step through the program, you notice that you only get a breakpoint on the ODOBNM I spec. That is because the RPG program didn't use any of the other fields, so the RPG compiler did an optimization to avoid loading the data for those other fields.

You also notice that you step to the I spec and DSPLY opcode twice (at least it was twice on my system, once for QRPGLESRC and once for QRPGSRC).

Using externally-described files

The previous program defined RPGTESTF as an externally-described file (to make it a program described file, we would have had to code DISK(100) or whatever the record length is). The RPG compiler "extracts" the file definition as part of the compile, so it knows the names and buffer positions of all the fields in the file.

Most RPG programmers use explicit loops to read a file, but they allow the RPG compiler to implicitly open and close the file. Let's try that.

  1. First, remove the USROPN keyword from the F spec.
  2. Now, remove the OPEN and CLOSE operations.
       dcl-f rpgtestf;

       read rpgtestf;
       dow not %eof;
          dsply ODOBNM;
          read rpgtestf;
       enddo;

The RPG compiler implicitly opens the file when you call your program.

But what about closing the file? The RPG compiler does not always close files when a program ends by using the RETURN operation. It only closes files when it finds the "Last Record" indicator, *INLR, to be on. You can simply set *INLR on at some point before reaching the end of calculations, or you can set on *INLR and immediately return. Many RPG programmers set *INLR on as the very first calculation, as a visible clue that the calculations are only meant to be run once. Other RPG programmers set *INLR on at the end of calculations. Either way works fine to cause the calculations to end and to cause the file to be closed.

Try compiling the program. Note that it doesn't have a RETURN operation or code to set *INLR on. The RPG compiler issues message RNF7023 saying that it doesn't know how the program will end. Without a RETURN operation, or *INLR on, the program just loops doing the calculations over and over.

Here is the final corrected program, with *INLR set on as the first statement in the calculations. (Remember that it doesn't matter where *INLR is set on, as long as it is on at the end of the calculations.)

       dcl-f rpgtestf;

       *inlr = '1';
       read rpgtestf;
       dow not %eof;
          dsply ODOBNM;
          read rpgtestf;
       enddo;

Other types of files

The previous examples all used a database file. The RPG device type for a database file is DISK. This is the default, so we didn't have to code it.

The other two most commonly used device types in RPG are printer files (PRINTER) and display files (WORKSTN). Display files are handled in a later chapter. Here is a little example of a program-described printer file.

       dcl-c QPRINT_LEN 132;
       dcl-f qprint printer(QPRINT_LEN);
       dcl-ds qprint_ds len(QPRINT_LEN) end-ds;

       *inlr = *on;
       qprint_ds = 'Hello';
       write qprint qprint_ds;
       qprint_ds = 'world';
       write qprint qprint_ds;

Points to note

  • The PRINTER keyword has a numeric parameter (QPRINT_LEN, which has the value 132). This makes it a program-described file. The RPG compiler does not try to find the file on the system to extract the record layout. Instead, the record layout is coded in the RPG program.
  • In this case, we are just using a flat 132-byte data structure to define the record layout. We specify the data structure as the second operand for the WRITE operation.
  • This program opens and closes the file implicitly. The file gets closed and the program ends after the WRITE statement because *INLR is on.
  • After you run this program, you can find a QPRINT file at the end of your spool files. It has two lines, Hello and world.

Output and update

The previous examples all opened the file for input only. The default usage for a DISK file is USAGE(*INPUT). If you want to write to the file, code USAGE(*OUTPUT). If you want to be able to update the file, code USAGE(*UPDATE). If you want to do both, code USAGE(*OUTPUT:*UPDATE).

Update example

       dcl-f rpgtestf usage(*update);

       read rpgtestf;
       dow not %eof;
          dsply 'new name' '' ODOBNM;
          update QLIDOBJD;
          read rpgtestf;
       enddo;
       *inlr = *on;

This program uses the UPDATE operation. Unlike the READ operation, which can be used with either a file name or a record format name, the UPDATE operation can only be used with a record format name. You can use DSPFD to find out the name of the format, or you can just look in one of the RPG compiler listings for the earlier versions of the program.

When you run this program, the DSPLY operation waits until you enter a value. The value you enter becomes the new value for the ODOBNM variable, and that value is used when the record is updated by the UPDATE operation. If you display the file by using DSPPFM, you see that the file name has been changed.

Output example

       dcl-f rpgtestf usage(*output);

       ODOBNM = 'ABCDE';
       write QLIDOBJD;
       *inlr = '1';

The WRITE operation also needs to have the record format name rather than the file name.

When you run this program, it adds a new record to the end of the file. In the new record, all the values are defaulted to blanks or zeros except the file name. Use DSPPFM to display the file again to see the new record.

To see the names of other fields you could set before you do the WRITE, use command DSPFFD RPGTESTF.

Exercises related to implicit open and close

Exercise 4-1

Using the example from 'Implicitly opening and closing the file' as an example, remove the assignment to *INLR, and add a RETURN operation at the end of the calculations.

       dcl-f rpgtestf;

       read rpgtestf;
       dow not %eof;
          dsply ODOBNM;
          read rpgtestf;
       enddo;
       return;

Call the program twice.

Why does the program only produce output the first time it is called?

Solution

Exercise 4-2

Using the version of the program with the USROPN keyword as an example, remove the CLOSE operation (you can just comment it out by using //).

       dcl-f rpgtestf usropn;

       open rpgtestf;
       read rpgtestf;
       dow not %eof;
          dsply ODOBNM;
          read rpgtestf;
       enddo;
       // close rpgtestf;
       return;

Call the program twice.

Why does the program get an error the second time it is called?

Solution

Chapter 5: Display files

This chapter introduces you to using display files, also called "workstation files".

First, let's create a display file

Paste the following source into a source member with the name RPGDSPF and the source type of DSPF. The file has one input field, NAME.

     A          R GETNAME
     A                                  5  5'What is your name?'
     A            NAME          25A  I  5 25
     A                                      CHECK(LC)

Compile the file. It creates a display file called RPGDSPF.

Show a screen

Compile and run the following program.

       dcl-f rpgdspf workstn;

       exfmt getname;
       dsply ('Your name is ' + name);
       *inlr = '1';

This program just shows the screen and reads the input value. Then it uses the DSPLY opcode to show the value that it got.

A more complex interaction

Change the display file source to add another record format. The new record format SHOWINFO has some input/output fields (identified by the B for "Both") and an output field. Recompile the source to get a new version of the file.

     A          R GETNAME
     A                                  5  5'What is your name?'
     A            NAME          25A  I  5 25
     A                                      CHECK(LC)
     A          R SHOWINFO                  CA03(03) CA05(05)
     A                                  5  5'Hello '
     A            NAME          25A  B  5 12
     A                                      CHECK(LC)
     A            CURTIME         T  O  9  2
     A                                  9 15'Current time'
     A                                      DSPATR(HI)
     A            CONDITION     10A  B 10  2
     A                                      CHECK(LC)
     A                                 10 15'Current condition'
     A                                 23  2'F3=Exit  F5=Refresh'
     A                                      COLOR(BLU)

Compile and run the following program. When you get to the part where it shows you the current time, try changing the condition to something else, say "Raining". The next time you see the screen, it shows "Raining". Try changing it to something else, say "Warm", but this time press F5 instead of pressing Enter. It shows "Raining" again. You can also change the name in the same way.

       dcl-f rpgdspf workstn;
       dcl-s done ind;

       exfmt getname;

       done = '0';
       condition = 'Cloudy';
       dow not done;
          curtime = %time();
          exfmt showinfo;
          select;
          when *in05;
             // Refresh, handled automatically
          when *in03;
             // Exit
             *inlr = '1';
             return;
          other;
             // Just show the screen again
          endsl;
          enddo;

What's going on in the RPG program?

Compile the program with the listing view DBGVIEW(*LIST) or DBGVIEW(*ALL), and run it under debug.

Set a breakpoint on the EXFMT opcode in the loop that works with the SHOWINFO record format.

Run the program, and see what happens on the EXFMT opcode.

  1. First, it goes to some generated Output specifications, which set the fields into the output buffer from the RPG program variables.
  2. Then it shows the screen.
  3. Then, after you press ENTER, it goes to the generated Input specifications where it loads the RPG program variables from the input buffer.
  4. Then it finally goes to the RPG SELECT statement following the EXFMT.
  5. If you pressed F3, the *IN03 indicator was set on. (Due to the CA03(03) coding in the display file source.)
  6. Similarly, if you pressed F5, *IN05 was set on.
  7. If you pressed ENTER, no indicator was set on.

Solutions to exercises

Exercise 3-1

       dcl-s myCharfield char(5);

       myCharField = 'hello';
       dsply myCharField;
       return;

Back to Exercise 3-1

Exercise 3-2

The CHAR data type always has the same length, so the variable contains trailing blanks, which are used when the variable is concatenated with other text.

Bonus question: What RPG built-in function could be used in the concatenation expression to produce the same output in the version where the variable is defined with the CHAR type rather than the VARCHAR type?

Back to Exercise 3-2

Exercise 4-1

When the RETURN opcode is used, and *INLR is not on when the program returns, the RPG compiler does not implicitly close the file. The next time the program is called, the file is still open and still at end of file, so subsequent READ operations do not find any records and the loop exits immediately.

Bonus activity to correct the problem: Add a SETLL *START operation for the file, before the READ loop. That sets up the file so it is positioned at beginning of file, and the program works the same both times.

       setll *start rpgtestf;

Back to Exercise 4-1

Exercise 4-2

When the RETURN opcode is used, the RPG compiler does not implicitly close the file. The next time the program is called, the OPEN operation fails because the file already open.

Bonus activity to correct the problem: Add a check to see whether the file is already open before using the OPEN opcode.

       if not %open(rpgtestf);
          open rpgtestf;
       endif;

Back to Exercise 4-2

[{"Business Unit":{"code":"BU058","label":"IBM Infrastructure w\/TPS"},"Product":{"code":"SS69QP","label":"Rational Development Studio for i"},"Component":"ILE RPG Compiler","Platform":[{"code":"PF012","label":"IBM i"}],"Version":"All Versions","Edition":"","Line of Business":{"code":"LOB57","label":"Power"}}]

Document Information

Modified date:
24 July 2020

UID

ibm11108749