Free-format RPG first: your first real program
Skip the fixed-format columns for now. Modern RPG is written in **FREE form: no column rules, semicolons, keywords like dcl-s. You'll still need to read fixed-format later (see Reading fixed-format RPG you inherited: the H/F/D/C spec decoder), but you should learn to write the modern way first.
This article builds one program that reads a table, loops over it, handles errors and prints with dsply.
- Shape of a **FREE program: **free, ctl-opt, statements end with ;
- Declarations: dcl-s, dcl-ds, dcl-f
- Sample table: CUSTMAST created with SQL
- Complete program: Read, loop, monitor, dsply
- Compile and run: CRTBNDRPG and CALL
- Try it on PUB400: Break it on purpose
The shape of a **FREE program
The first line must be **free, starting in column 1. Everything after it is free-format. Statements end with ;. Comments start with //.
**free
ctl-opt dftactgrp(*no) actgrp(*new) option(*srcstmt:*nodebugio);
dsply 'Hello from RPG';
return;
Same program in fixed format (what you will meet in most shops):
H OPTION(*SRCSTMT:*NODEBUGIO) DFTACTGRP(*NO) ACTGRP(*NEW)
DMSG C CONST('Hello from RPG')
C MSG DSPLY
C RETURN
In fixed format DSPLY takes the message in factor 1, and factor 1 is only 14 characters wide (columns 12-25), so the literal lives in a named constant. H keywords start after column 6; I put them at column 8 (not sure every shop aligns them the same way).
ctl-opt sets compile and runtime options. dftactgrp(*no) is required if you want to use ILE features like procedures and bound calls (see ILE modules, service programs and the binder). option(*srcstmt) makes error line numbers match your source lines, which saves you a lot of grief.
Declarations
dcl-s count int(10) inz(0); // integer
dcl-s total packed(11:2) inz(0); // decimal, 11 digits, 2 after the point
dcl-s name varchar(50); // variable length text
dcl-s fixedName char(30); // fixed length, blank padded
dcl-s today date; // date type
dcl-s done ind inz(*off); // true/false indicator
dcl-ds summary qualified; // data structure = a struct
rows int(10);
bigOnes int(10);
end-ds;
Same program in fixed format (what you will meet in most shops):
DCOUNT S 10I 0 INZ(0)
DTOTAL S 11P 2 INZ(0)
DNAME S 50A VARYING
DFIXEDNAME S 30A
DTODAY S D
DDONE S N INZ(*OFF)
DSUMMARY DS QUALIFIED
D ROWS 10I 0
D BIGONES 10I 0
DS/S sit in columns 24-25, length ends in column 39, data type in column 40, decimals in 41-42. A DATE has no length, so columns 33-39 stay blank. Fixed-format comments need * in column 7, so the free-format // remarks are dropped.
-
dcl-sdeclares a standalone variable. -
dcl-ds ... end-dsdeclares a data structure. Withqualifiedyou access fields assummary.rows, like a struct. -
charis fixed-length and padded with blanks. That surprises web developers.varcharbehaves more like a string. -
dcl-fdeclares a file. More on that next.
The sample table
Create a table on PUB400 or any IBM i. Use the SQL scripts feature in VS Code, or STRSQL, or RUNSQLSTM:
CREATE TABLE MYLIB.CUSTMAST (
CUSTID INTEGER NOT NULL,
CUSTNAME CHAR(30) NOT NULL,
CITY CHAR(20) NOT NULL,
BALANCE DECIMAL(9,2) NOT NULL
);
INSERT INTO MYLIB.CUSTMAST VALUES
(1, 'Ana Santos', 'Manila', 1500.50),
(2, 'Budi Hartono', 'Jakarta', 320.00),
(3, 'Chen Wei', 'Singapore', 2750.75);
Replace MYLIB with your own library (see the mental model of libraries and objects). Since the table has no explicit field naming conflicts, RPG will read the field names from the table at compile time.
The complete program
Save as CUSTRPT.rpgle in the IFS, or as a member in QRPGLESRC:
**free
ctl-opt dftactgrp(*no) actgrp(*new) option(*srcstmt:*nodebugio);
dcl-f custmast usage(*input);
dcl-s today date;
dcl-s line varchar(80);
dcl-s pos int(10);
dcl-s i int(10);
dcl-s status char(8);
dcl-ds summary qualified;
rows int(10) inz(0);
total packed(11:2) inz(0);
bigOnes int(10) inz(0);
end-ds;
today = %date();
dsply ('Customer report ' + %char(today:*iso));
monitor;
read custmast;
dow not %eof(custmast);
summary.rows += 1;
summary.total += balance;
select;
when balance >= 2000;
status = 'HIGH';
summary.bigOnes += 1;
when balance >= 1000;
status = 'MEDIUM';
other;
status = 'LOW';
endsl;
line = formatLine(custname: city: balance: status);
dsply line;
read custmast;
enddo;
on-error;
dsply 'Could not read CUSTMAST';
endmon;
pos = %scan('Singapore': 'Ships to Singapore today');
if pos > 0;
dsply ('Found at ' + %char(pos));
endif;
for i = 1 to 3;
dsply ('Pass ' + %char(i));
endfor;
dsply ('Rows: ' + %char(summary.rows)
+ ' Total: ' + %char(summary.total)
+ ' High: ' + %char(summary.bigOnes));
return;
dcl-proc formatLine;
dcl-pi *n varchar(80);
nm char(30) const;
ct char(20) const;
bal packed(9:2) const;
st char(8) const;
end-pi;
return %trim(nm) + ' | ' + %trim(ct) + ' | ' + %char(bal) + ' | ' + %trim(st);
end-proc;
Same program in fixed format (what you will meet in most shops):
H DFTACTGRP(*NO) ACTGRP(*NEW) OPTION(*SRCSTMT:*NODEBUGIO)
FCUSTMAST IF E DISK
DERRMSG C CONST('Could not read CUSTMAST')
DMSG S 100A VARYING
DTODAY S D
DLINE S 80A VARYING
DPOS S 10I 0
DI S 10I 0
DSTATUS S 8A
DSUMMARY DS QUALIFIED
D ROWS 10I 0 INZ(0)
D TOTAL 11P 2 INZ(0)
D BIGONES 10I 0 INZ(0)
DFORMATLINE PR 80A VARYING
D NM 30A CONST
D CT 20A CONST
D BAL 9P 2 CONST
D ST 8A CONST
C EVAL TODAY = %DATE()
C EVAL MSG = 'Customer report ' + %CHAR(TODAY:*ISO)
C MSG DSPLY
C MONITOR
C READ CUSTMAST
C DOW NOT %EOF(CUSTMAST)
C EVAL SUMMARY.ROWS = SUMMARY.ROWS + 1
C EVAL SUMMARY.TOTAL = SUMMARY.TOTAL + BALANCE
C SELECT
C WHEN BALANCE >= 2000
C EVAL STATUS = 'HIGH'
C EVAL SUMMARY.BIGONES = SUMMARY.BIGONES + 1
C WHEN BALANCE >= 1000
C EVAL STATUS = 'MEDIUM'
C OTHER
C EVAL STATUS = 'LOW'
C ENDSL
C EVAL LINE = FORMATLINE(CUSTNAME: CITY:
C BALANCE: STATUS)
C LINE DSPLY
C READ CUSTMAST
C ENDDO
C ON-ERROR
C ERRMSG DSPLY
C ENDMON
C EVAL POS = %SCAN('Singapore':
C 'Ships to Singapore today')
C IF POS > 0
C EVAL MSG = 'Found at ' + %CHAR(POS)
C MSG DSPLY
C ENDIF
C FOR I = 1 TO 3
C EVAL MSG = 'Pass ' + %CHAR(I)
C MSG DSPLY
C ENDFOR
C EVAL MSG = 'Rows: ' + %CHAR(SUMMARY.ROWS)
C + ' Total: ' + %CHAR(SUMMARY.TOTAL)
C + ' High: ' + %CHAR(SUMMARY.BIGONES)
C MSG DSPLY
C RETURN
PFORMATLINE B
DFORMATLINE PI 80A VARYING
D NM 30A CONST
D CT 20A CONST
D BAL 9P 2 CONST
D ST 8A CONST
C RETURN %TRIM(NM) + ' | ' + %TRIM(CT) + ' | '
C + %CHAR(BAL) + ' | ' + %TRIM(ST)
PFORMATLINE E
Fixed format needs the prototype (D FORMATLINE PR) declared before the first call; dcl-proc generates it for you. Names are uppercased by habit, columns are as I recall them: I am not certain about the H keyword column, so check it on PUB400.
What each part is doing:
-
dcl-f custmast usage(*input);opens the table for reading. Its columns (CUSTID,CUSTNAME,CITY,BALANCE) become variables automatically. -
readreads the next record;%eof(custmast)is true once there is nothing left. - The
readis done before the loop and again at the bottom. That's the standard pattern. -
select / when / other / endslis a switch.ifanddowwork as you'd expect;forcounts upward. -
monitor / on-error / endmonis try/catch.on-errorwith no code catches everything. - Procedures (
dcl-proc,dcl-pi) go after the main logic.*nmeans the procedure is unnamed in the interface; the name is ondcl-proc.constpasses by read-only reference. -
dsplyshows a message on the screen and in the job's message queue. It truncates at 52 characters, which is fine for a first program. For a real report, use a printer file withdcl-f ... printer, or write to the IFS.
Built-in functions you'll use daily
| Function | What it does | Example |
|---|---|---|
%char(x) |
number or date to text | %char(balance) |
%dec(x:p:s) |
text to packed decimal | %dec('123.45':7:2) |
%trim(s) |
strip blanks both ends | %trim(custname) |
%scan(find:in) |
position of text, 0 if not found | %scan('A':name) |
%subst(s:start:len) |
slice of a string | %subst(name:1:3) |
%date() |
today, or convert a value to a date | %date() |
%subst is 1-based. %scan returns 0 when not found, not -1.
Compile and run
From a 5250 session or the VS Code terminal:
CRTBNDRPG PGM(MYLIB/CUSTRPT) SRCSTMF('/home/YOURUSER/CUSTRPT.rpgle') DBGVIEW(*SOURCE)
CALL PGM(MYLIB/CUSTRPT)
If your source is in a source physical file instead:
CRTBNDRPG PGM(MYLIB/CUSTRPT) SRCFILE(MYLIB/QRPGLESRC) SRCMBR(CUSTRPT) DBGVIEW(*SOURCE)
CRTBNDRPG compiles and binds in one step. If it fails, read the compile listing: DSPJOBLOG or WRKSPLF, or let Code for IBM i show the errors in the editor.
Naming conventions
- Object names: up to 10 characters, uppercase, start with a letter.
CUSTRPT, notcustomer_report_final. - Source members: same name as the object they build.
- Variables: camelCase is common in modern code (
bigOnes), and RPG is case-insensitive anyway. - Source files:
.rpglefor RPG,.sqlrpgleif it has embedded SQL,.cllefor CL. - Prefix or qualify data structure fields so they don't collide with file fields.
Common beginner errors
- `free` not in column 1.** Compiler treats the source as fixed-format and fills the listing with errors.
- Missing semicolon. The error usually points to the line after the real problem.
-
Forgetting
dftactgrp(*no)when you use procedures or binding. You'll get activation group errors. -
Comparing
charfields with trailing blanks and wondering why they don't match. Use%trimon both sides. - Name conflicts. A variable named the same as a file field gives an "already defined" error.
-
Library not in the library list.
CUSTMASTisn't found at compile time. RunADDLIBLE MYLIBfirst, or qualify the file name. -
dsplyseems to hang. It waits for you to answer the message if you gave it a response variable. Without one it should just show. If a job sits inMSGW, checkWRKACTJOB.
Try it on PUB400
- Sign on and make sure
DSPLIBLshows your library, or add it withADDLIBLE. - Run the
CREATE TABLEandINSERTabove usingRUNSQLSTMor VS Code's SQL runner. - Upload or type
CUSTRPT.rpgleinto your IFS home directory. - Run the
CRTBNDRPGcommand. Fix any errors it lists. -
CALL PGM(MYLIB/CUSTRPT)and watch thedsplylines. - Break it on purpose: rename the table in
dcl-fand read the compile message. Then add a%subst(custname:1:5)to the output line.
Best resources
- IBM Docs, ILE RPG Reference: the final word on every keyword and built-in function
-
RPGPGM.com: Simon Hutchinson's detailed posts on free-format RPG and
**FREEconversions - Code for IBM i docs: compile from VS Code and see errors inline
- COMMON RPG Bootcamp: structured training material for modern RPG
- Barrett Otte, IBMi-Book: free community book with RPG chapters
Next
Next: Reading fixed-format RPG you inherited
Related
Originally published on rainierio.dev. The full Learn IBM i track, with an Indonesian version, is at rainierio.dev/learn.
Top comments (0)