Coding in RPG IV - A Beginner's Tutorial
Coding in RPG IV - A Beginner's Tutorial
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 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.
Back to top
RDi
Initial setup
If you are using RDi:
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 member HELLO in the file by using F6 in WRKMBRPDM or by using the ADDPFM
command, giving it type RPGLE.
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.
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
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.
Delete the old version of the program by using the DLTPGM command.
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.
That's it!
From now on, I'll assume you know
Back to top
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.
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.
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.
Calculation statements, also called "C specs". Here is where you code the logic of your procedures.
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.
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:
There are subtle differences in these two mechanisms that are discussed in later chapters.
Back to top
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.
dsply max_elems;
dsply default_city_name;
return;
Paste the following code into a source member, then compile and run it.
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.
The %char built-in function, which converts the numeric value to a readable character form such as
"-12.345".
Exercise 3-1
Define a standalone field of type character with length 5. The data type keyword for character is CHAR.
Code an assignment statement to set the character field to 'hello'.
Solution
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.
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;
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 %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".
Paste the following code into a source member, then compile and run it.
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;
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
Change the data type for NAME to CHAR instead of VARCHAR.
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.
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.
/if defined(*CRTBNDRPG)
ctl-opt dftactgrp(*no)
actgrp(*new);
/endif
ctl-opt option(*srcstmt);
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;
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.
ACTGRP(*NEW): This keyword sets the activation group for the program.
OPTION(*SRCSTMT). This keyword causes the compile listing to have the same statement numbers
as the source file. Most RPG programmers use this keyword.
Back to top
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)
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.
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.
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).
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;
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.
*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.
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.
dcl-f rpgtestf;
read rpgtestf;
dow not %eof;
dsply ODOBNM;
read rpgtestf;
enddo;
return;
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 //).
open rpgtestf;
read rpgtestf;
dow not %eof;
dsply ODOBNM;
read rpgtestf;
enddo;
// close rpgtestf;
return;
Why does the program get an error the second time it is called?
Solution
Back to top
A R GETNAME
A 5 5'What is your name?'
A NAME 25A I 5 25
A CHECK(LC)
Show a screen
Compile and run the following program.
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 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.
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;
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.
First, it goes to some generated Output specifications, which set the fields into the output buffer from the
RPG program variables.
Then, after you press ENTER, it goes to the generated Input specifications where it loads the RPG program
variables from the input buffer.
Then it finally goes to the RPG SELECT statement following the EXFMT.
If you pressed F3, the *IN03 indicator was set on. (Due to the CA03(03) coding in the display file source.)
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?
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.
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;