Dev.to · 9 min read

Free-format RPG first: your first real program

Free-format RPG first: your first real program

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-s declares a standalone variable. dcl-ds ... end-ds declares a data structure. With qualified you access fields as summary.rows, like a struct. char is fixed-length and padded with blanks. That surprises web developers. varchar behaves more like a string. dcl-f declares 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. read reads the next record; %eof(custmast) is true once there is nothing left. The read is done before the loop and again at the bottom. That's the standard pattern. select / when / other / endsl is a switch. if and dow work as you'd expect; for counts upward. monitor / on-error / endmon is try/catch. on-error with no code catches everything. Procedures (dcl-proc, dcl-pi) go after the main logic. *n means the procedure is unnamed in the interface; the name is on dcl-proc. const passes by read-only reference. dsply shows 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 with dcl-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, not customer_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: .rpgle for RPG, .sqlrpgle if it has embedded SQL, .clle for 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 char fields with trailing blanks and wondering why they don't match. Use %trim on both sides. Name conflicts. A variable named the same as a file field gives an "already defined" error. Library not in the library list. CUSTMAST isn't found at compile time. Run ADDLIBLE MYLIB first, or qualify the file name. dsply seems 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 in MSGW, check WRKACTJOB. Try it on PUB400 Sign on and make sure DSPLIBL shows your library, or add it with ADDLIBLE. Run the CREATE TABLE and INSERT above using RUNSQLSTM or VS Code's SQL runner. Upload or type CUSTRPT.rpgle into your IFS home directory. Run the CRTBNDRPG command. Fix any errors it lists. CALL PGM(MYLIB/CUSTRPT) and watch the dsply lines. Break it on purpose: rename the table in dcl-f and 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 **FREE conversions 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 The IBM i mental model CL programming essentials SQLRPGLE: embedded SQL done properly Originally published on rainierio.dev. The full Learn IBM i track, with an Indonesian version, is at rainierio.dev/learn.

This is a summary aggregated from Dev.to. Read the complete article on the original site:

Read full article at Dev.to

More Programming & Dev News