Free-format RPG: A practical guide for intermediate developers

You already write RPG. You know what a file is, you’ve written READ and WRITE operations, and you’ve probably maintained your share of fixed-format code that looks like it was typed on a punch card — because it was designed exactly for that.

Free-format RPGLE doesn’t replace what you know. It just removes the straitjacket. No more counting columns. No more squeezing logic into 80 characters. No more cryptic two-letter opcodes that made sense in 1978.

This guide walks you through the shift — practically, with real code comparisons — so you can start writing modern RPGLE today.

What actually changed

Traditional fixed-format RPG forced you to put everything in specific columns. Indicators in columns 7-8, factor 1 in 12-25, opcode in 26-35, factor 2 in 36-49. Miss a column and you get a compile error that makes no sense.

Free-format throws all of that out. You write code the way you think — left to right, with proper keywords, in any column you like. The compiler figures out the rest.

Here is the same logic written both ways:

Fixed-format (old way)

     C                   IF        Salary > 50000
     C                   EVAL      TaxRate = 0.30
     C                   ELSE
     C                   EVAL      TaxRate = 0.20
     C                   ENDIF

Free-format (modern way)

if Salary > 50000;
    TaxRate = 0.30;
else;
    TaxRate = 0.20;
endif;

Same logic. One is readable. The other looks like it belongs in a museum.

How to enable free-format in your source

You need one control spec at the top of your source member. This tells the compiler you are writing free-format:

**FREE

That is it. Put **FREE on line 1, column 1, and the entire source member is now free-format. No mixed modes, no column restrictions.

If you are working in an older source member and cannot convert the whole thing yet, you can use the /FREE and /END-FREE directives to free-format individual sections — but for any new code you write, just use **FREE from the top.

The specs you will use every day

Control spec

**FREE

ctl-opt dftactgrp(*no) actgrp(*caller) option(*srcstmt *nodebugio);

dftactgrp(*no) is important — it tells the system this program uses ILE activation groups, which you need for subprocedures and service programs. Always include it in new programs.

File spec

dcl-f EMPLOYEEPF disk(*ext) usage(*input) keyed;

dcl-f declares a file. Much more readable than the old F-spec columns. You can see exactly what the file is doing — disk, external, input only, keyed access.

Data definitions

dcl-s EmployeeName varchar(50);
dcl-s Salary      packed(9:2);
dcl-s TaxRate     packed(5:4);
dcl-s Counter     int(10)      inz(0);

dcl-ds AddressDS;
    Street  varchar(100);
    City    varchar(50);
    Pincode char(6);
end-ds;

Control flow — if, dow, for

IF / ELSEIF / ELSE

if Department = 'SALES';
    Bonus = Salary * 0.15;
elseif Department = 'IT';
    Bonus = Salary * 0.12;
else;
    Bonus = Salary * 0.08;
endif;

DOW — Do While

dow not %eof(EMPLOYEEPF);
    read EMPLOYEEPF;
    if not %eof(EMPLOYEEPF);
        TotalSalary += Salary;
        Counter += 1;
    endif;
enddo;

FOR loop

for i = 1 to %elem(MonthlyArray);
    AnnualTotal += MonthlyArray(i);
endfor;

Built-in functions you will use constantly

// String handling
dcl-s FullName varchar(100);
FullName = %trimr(FirstName) + ' ' + %trimr(LastName);

// Numeric checks
if %dec(InputField: 7: 2) > 0;
    // process it
endif;

// Date handling
dcl-s Today date(*iso);
Today = %date();
dcl-s DaysLeft int(5);
DaysLeft = %diff(DueDate: Today: *days);

// EOF and found checks
read EMPLOYEEPF;
if %eof(EMPLOYEEPF);
    leave;
endif;

chain EmployeeID EMPLOYEEPF;
if %found(EMPLOYEEPF);
    // record found
endif;

Subprocedures — the big upgrade

If you are still writing everything in the main procedure, free-format is the right time to change that habit. Subprocedures let you break logic into reusable, testable chunks — the same idea as functions in any modern language.

**FREE

ctl-opt dftactgrp(*no) actgrp(*caller);

dcl-s EmpSalary packed(9:2);
dcl-s FinalTax  packed(9:2);

EmpSalary = 75000;
FinalTax = CalcTax(EmpSalary);
dsply FinalTax;

*inlr = *on;
return;

dcl-proc CalcTax;
    dcl-pi *n packed(9:2);
        pSalary packed(9:2) value;
    end-pi;

    dcl-s TaxAmount packed(9:2);

    if pSalary > 50000;
        TaxAmount = pSalary * 0.30;
    else;
        TaxAmount = pSalary * 0.20;
    endif;

    return TaxAmount;
end-proc;

A complete working example

**FREE

ctl-opt dftactgrp(*no) actgrp(*caller) option(*srcstmt);

dcl-f EMPLOYEEPF disk(*ext) usage(*input);

dcl-s TotalPayroll packed(13:2) inz(0);
dcl-s EmpCount     int(10)      inz(0);
dcl-s AvgSalary    packed(11:2);

read EMPLOYEEPF;
dow not %eof(EMPLOYEEPF);
    TotalPayroll += SALARY;
    EmpCount     += 1;
    read EMPLOYEEPF;
enddo;

if EmpCount > 0;
    AvgSalary = TotalPayroll / EmpCount;
endif;

dsply ('Total Payroll: ' + %char(TotalPayroll));
dsply ('Employees: '     + %char(EmpCount));
dsply ('Avg Salary: '    + %char(AvgSalary));

*inlr = *on;
return;

Common mistakes when switching

Forgetting the semicolon. Every statement ends with a semicolon in free-format. Coming from fixed-format this trips everyone up in the first week. The compiler error will say something unhelpful — just look for a missing ;.

Mixing free and fixed in the same spec. If you use **FREE at the top, the entire source is free-format. You cannot go back to fixed columns mid-member without the directives.

Leaving out dftactgrp(*no). If you try to call a subprocedure or use dcl-proc without this in your control spec, you will get a compile error about activation groups. Always include it.

Using old opcodes out of habit. EVAL, MOVE, MOVEL — these work in free-format but they are the old way. Replace EVAL X = Y with just X = Y. Replace MOVE with proper assignment or %subst.

What to do right now

Do not try to convert your entire codebase. Pick one small utility program — something you understand completely — and rewrite it in free-format with **FREE. Keep the original. Compare them. Compile and test the new one.

That first rewrite is where it clicks. You will spend the next hour wondering why you did not switch sooner.

The next article in this series covers subprocedures and service programs — how to build reusable libraries of RPGLE code the right way. If you are writing the same logic in three different programs, that is the article you need.

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top