Coding in Free-Form RPG IV - a beginner's tutorial

From MidrangeWiki
Jump to: navigation, search

Contents

Coding in Free-Form RPG IV - a beginner's tutorial

Originally by Barbara Morris, IBM Toronto Language Lab Posted to DeveloperWorks

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. If you are on an earlier version, use the older form of this tutorial: Coding in RPG IV - a beginner's tutorial
  • that you have no prior knowledge of RPG III
  • that you have some prior knowledge of IBM i (aka AS/400, iSeries, System i)
  • that you know where to find the manuals for RPG in the Info 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 will list additional interesting things to note about the example, usually things that have not been discussed at all yet. Readers can choose to ignore those "Bonus features" sections without worrying they will miss something, since the material will be covered in later chapters.

Chapter 1: Coding in Free-Form RPG IV - Chapter 1 Hello World

This chapter will take 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:

  1. RDi (or its earlier cousins RDPower or WDSC)
  2. 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'll 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 PDM and SEU:

  • logon to a session on your IBM i
  • create a library using the CRTLIB command
  • create a source file using the CRTSRCPF command
  • WRKMBRPDM specifying your library and source file
  • create a source member HELLO in the file using F6 in WRKMBRPDM or using the ADDPFM command, giving it type RPGLE
  • open the member in SEU by using the "2" option
   Warning: SEU will give errors for every free-form H, F, D or P statement. SEU has not been updated since 6.1, so it does not understand the new syntax. If you still want to use SEU, you will have to exit with errors after saving 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 will show you all the columns. If you are using SEU, the editor will not show you columns 1-5 by default, so the first column you see will be 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 using F3, and compile using option 14. You could also compile from the command line using the CRTBNDRPG command, and using F4 to prompt the command.
  • If you are using RDi, log on to a session on the IBM i 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 will halt. If you decide not to use WRKOBJPDM, or if it's not available, then your program will just end without letting you see the DSPLY. In that case, use DSPJOBLOG, then F10, then F6, to see the output.)

  • (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 should put 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 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 will assume you have **FREE at the beginning of your source, but it will also work if you use column-limited source, as long as you ensure that columns 1-7 are blank and that you don't go past column 80. The examples will 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 will be an undefined variable and will generate a compiler error.
    dsply name;
    return;
  • Delete the old version of the program using the DLTPGM command.
  • Compile the program again. It will fail with a severity 30 error.
  • If you are using RDi, click on the RNF7030 error in the Error List Window. It should highlight 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 top of the file and enter RNF7030 on the search line, then hit F16 to locate the error message in the listing. The error message will have a statement number (statement 2). Page back in the listing to find statement 2 (on the left hand side of the listing). It should be the line that tried to display the "name" variable.
  • The error message indicates that NAME is not defined. To correct it, you'll 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 upper-case form of the name will appear 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 only use columns 8 - 80.
     dcl-s name char(10) inz('Jim');
     dsply name;
     return;
  • Recompile the program and call it again. It should put 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 user-friendly 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 will stop 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: Coding in Free-Form RPG IV - Chapter 2 General info about RPG

This chapter will introduce you to general information about RPG source code.

RPG source code

For free-form statements, the RPG compiler only uses 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 those 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". This 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. For the other types of statements, this tutorial will only discuss the free-form versions of the statements.

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 you will learn in later chapters.

Chapter 3:Coding in Free-Form RPG IV - Chapter 3 Variables and procedures

This chapter will introduce you to defining contants, 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

The 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.

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 using the LIKEDS keyword, or like a record format 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 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 only supports 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 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) = dates(1) + %days(1);   // tomorrow
      dates(3) = dates(1) + %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

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


      dcl-ds person qualified;
         name varchar(25);
         age packed(5);
      end-ds;
      dcl-ds families qualified dim(5);
         address varchar(50);
         numPeople uns(3);
         people likeds(person) dim(8);
      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 "people" subfield of the "families" data structure is defined with the LIKEDS 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.


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;
         cmdLne 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 say 'QcmdExc' in the EXTPGM keyword.
  • The CONST keyword indicates that the called program will 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 will create 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 using 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 will upper-case 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;
         cmdLne packed(15 : 5) const;
         dbcs char(3) const options(*nopass);
      end-pr;


Example 2: Call a procedure

So far in these examples, the DSPLY opcode has been used. DSPLY has many limitations, including a 52-byte maximum. DSPLY also prints to the external message queue which is not always desirable.

For this example, we'll 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 will create a temporary variable with a "null-terminated" version of the parameter. printf() assumes that the first parameter will be 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" will be defined, and the two H specs between the /IF and the /ENDIF will be used in the compile. If the CRTRPGMOD command is used, those two lines will not be 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.


Solutions to exercises

Exercise 3-1

      dcl-s myCharfield char(5);
      myCharField = 'hello';
      dsply myCharField;
      return;

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?

Chapter 4: Coding in Free-Form RPG IV - Chapter 4 Introduction to files

This chapter will introduce 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 will 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 will produce 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 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 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 will have to choose the listing view while you are debugging.

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

When you first see the debug listing view, it will look very different from your original code. You will 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 will 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 will 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.

Implicitly opening and closing 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 will implicitly open 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 above. Notice that it doesn't have a RETURN operation or code to set *INLR on. The RPG compiler will give message RNF7023 saying that it doesn't know how the program will end. Without a RETURN operation, or *INLR on, the program will just loop doing the calculations over and over.

Here is the final corrected program, with *INLR set on as the first statement in 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 will 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 will 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 will find a QPRINT file at the end of your spool files. It will have 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, our 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 will wait until you enter a value. The value you enter will become the new value for the ODOBNM variable, and that value will be used when the record is updated by the UPDATE operation. If you display the file using DSPPFM, you will 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 will add a new record to the end of the file. In the new record, all the values will be 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?

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 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?

Solutions to exercises

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 will not find any records and the loop will exit immediately.

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

      setll *start rpgtestf;

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 the file already open.

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

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


Chapter 5: Coding in Free-Form RPG IV - Chapter 5 Display files

This chapter will introduce 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 will create 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 will show "Raining" - try changing it to something else, say "Warm", but this time press F5 instead of pressing Enter. It will show "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.