Skip to content

Fixed Format Source

Daniel Long edited this page Sep 29, 2026 · 2 revisions

Fixed-Format Source

OpenRPG accepts classic column-based RPG IV source as well as free-format. This is a second parsing frontend only — it builds the same AST, so every language feature described elsewhere in this guide is reachable from fixed-format source.

How the Format Is Chosen

The format is detected from the content of columns 1-6 of the first substantive line, not from the file extension and not from a **FREE directive. A file can also mix the two: H/F/D specs in columns with the calculations already modernized inside /free.../end-free blocks is a common incremental-modernization pattern and works as-is.

Spec Types

Position 6 selects the spec type. Positions 1-5 are the sequence number and are ignored; a * in position 7 makes the whole line a comment.

Position 6 Spec Purpose
H Control Compile options — same set as CTL-OPT
F File Description File declarations — same as DCL-F
D Definition Standalone fields and data structures — same as DCL-S and DCL-DS
I Input Program-described input record layout (see next chapter)
C Calculation Executable statements
O Output Program-described output record layout (see next chapter)

There is no P-spec support, so fixed-format source cannot declare a procedure. Anything needing DCL-PROC — including ON-EXIT — has to live in a /free block.

H-Spec — Control Options

Positions 7-80 are a free keyword list, identical to what CTL-OPT accepts:

     HDFTACTGRP(*NO) DATFMT(*ISO) TIMFMT(*HMS)

F-Spec — File Description

     FCUSTFL    IF   E             DISK    KEYED EXTDESC('customers')
     FCUSTMENU  CF   E             WORKSTN
     FTESTFL    O    F  25         DISK
Positions Field
7-16 File name
17 File type (I/O/U/C)
18 File designation
19 End-of-file flag
20 Add flag
21 Sequence
22 File format (E externally described, F program described)
23-27 Record length (program-described files)
28 Process mode
29-33 Key field length
34 Record address type
35 File organization
36-42 Device — DISK and WORKSTN are implemented
44-80 Keyword tail, continuable across lines

D-Spec — Definitions

     DcustName         S             30A
     DorderDS          DS
     D  orderNo                       7P 0
     D  custName2                    30A   VARYING
     D  qty                           5P 0 DIM(12)
Positions Field
7-21 Name — continuable with a trailing ...
22 External-description flag
23 Data structure type
24-25 Definition type (blank, C, DS, PR, PI, S)
26-32 From position
33-39 To position or length
40 Data type
41-42 Decimal positions
44-80 Keyword tail, continuable across lines

Only definition types S (standalone field), DS (data structure) and blank (a subfield) are accepted. C for a named constant, and PR/PI for prototypes and procedure interfaces, are rejected — declare those in a /free block instead.

Supported in the keyword tail: VARYING{(2|4)} (which produces a VARCHAR — free-format spells this VARCHAR(n) instead), DIM(n) on the data structure itself for an array of elements, and per-subfield OVERLAY(field), OVERLAY(field:pos), LIKEDS(name), LIKE(other) and DIM(n). POS is a free-form keyword and is rejected here (IBM: RNF3555): a fixed-form subfield gives its From and To positions in 26-32 and 33-39, and its size follows from them (a 4-byte I subfield holds 10 digits, n packed bytes 2n-1):

     Drecord           DS                  QUALIFIED
     Did                       1      4I 0
     Dname                     5     24A

A name longer than the name positions continues with ... on a line of its own, and the rest of the definition goes on the line that completes it (IBM: RNF0622):

     DcustomerNumb...
     Der               S             10I 0

A subfield DIM(n) is an array inside the structure, and is subscripted with the ds.field(index) form:

     DorderDS          DS
     D  qty                           5P 0 DIM(12)
      /free
  orderDS.qty(1) = 5;
      /end-free

A subfield LIKE(other) resolves against a field declared earlier in the same data structure. In a QUALIFIED data structure that sibling is named LIKE(ds.other); alone, other is not defined there (IBM: RNF7030). Combining LIKE and DIM on one subfield is not supported, and a subfield DIM is fixed-size only — no DIM(*VAR)/DIM(*AUTO).

C-Spec — Calculations

Calculations come in three shapes, and all three can be mixed freely in one file.

Positions Field
7-8 SR, or AN/OR for a multi-indicator group
9-11 Conditioning indicator
12-25 Factor 1
26-35 Operation code, plus any (extender)
36-49 Factor 2 (traditional syntax)
36-80 The whole expression (extended factor 2)
50-63 Result (traditional syntax)
71-76 Resulting indicators — blank except on COMP

Positions 64-70 (inline field length and decimals) must be blank; declare the field on a D-spec instead.

Extended factor 2 puts a normal free-format expression in positions 36-80, and continues onto further lines that leave 7-35 blank:

     C                   IF        custBal > 1000 AND status = 'A'
     C                   EVAL      msg = 'Dear ' + %TRIM(firstName) +
     C                                   ', your account is current.'
     C                   ENDIF

Traditional syntax uses Factor 1, Factor 2 and Result:

     C     custKey       CHAIN     CUSTFL
     C                   EXSR      PRTLINE
     C     total         DSPLY

/free blocks hand their contents to the free-format parser unchanged:

      /free
  total = qty * price;
  DSPLY %CHAR(total);
      /end-free

Supported Operation Codes

Group Opcodes
Block structure IF ELSEIF ELSE ENDIF DOW DOU ENDDO FOR FOR-EACH ENDFOR SELECT WHEN OTHER ENDSL MONITOR ON-ERROR ENDMON ITER LEAVE
Subroutines BEGSR ENDSR EXSR LEAVESR
Assignment EVAL EVALR EVAL-CORR CLEAR RESET MOVE MOVEL Z-ADD Z-SUB
Arithmetic ADD SUB MULT DIV
Comparison / branching COMP CASxx CABxx GOTO TAG
Calls CALLP CALL PARM PLIST RETURN
File I/O CHAIN READ READP READE READPE WRITE UPDATE DELETE SETLL SETGT
Other DSPLY SORTA XML-INTO DATA-INTO DATA-GEN SND-MSG

Two notes on the file opcodes: none of them takes a data-structure result operand, and DELETE takes no key — it deletes the last record read. SETLL and SETGT do not accept an operation extender; the other file opcodes do.

Conditioning Indicators

Position 9 holds a blank or N, and positions 10-11 the indicator number. The statement runs only when the indicator matches:

     C   10              EVAL      msg = 'Ten is on'
     C  N10              EVAL      msg = 'Ten is off'

Only self-contained statements can be conditioned — assignments, calls, ITER/LEAVE/LEAVESR, the file opcodes, GOTO, DSPLY, and the arithmetic set. Conditioning a block-structure opcode is a compile error, because the wrapper would leave the block unbalanced; fold the test into the block's own condition instead. TAG cannot be conditioned either.

A C-spec line has room for exactly one indicator, so AN/OR in positions 7-8 combine the indicators of consecutive lines. The operation code sits on the last line of the group, and RPG relates the terms as an OR of AND-groups:

     C   10
     CAN 30
     COR 20              EVAL      msg = '(10 and 30) or 20'

Only *IN01-*IN99 are supported. LR, MR, RT, OV, 1P, L1-L9, H1-H9, U1-U8, KA-KY and OA-OG are recognized by name so they get a clear "not supported" message rather than being reported as a typo.

Legacy Operation Codes

GOTO and TAG have no free-format equivalent at all, so they are accepted only from fixed columns — writing either inside a /free block is an error. Since subroutines generate as lambdas, a GOTO cannot cross a subroutine boundary, which is RPG's own rule. Both take their label in Factor 2, and leave Factor 1 and the Result field blank.

     C                   GOTO      SKIP
     C                   EVAL      msg = 'not reached'
     C                   TAG       SKIP

ADD/SUB/MULT/DIV use Factor 1 when it is present and accumulate into the result when it is blank. Z-ADD/Z-SUB never use Factor 1.

Written Means
C a ADD b c c = a + b
C ADD b c c = c + b
C Z-ADD b c c = b
C Z-SUB b c c = -b

Extenders such as (H) half-adjust pass straight through.

MOVE and MOVEL are positional moves, not assignments. MOVE aligns factor 2 against the right end of the result and MOVEL against the left, and whatever part of the result the move does not reach is left unchanged — the (P) extender blanks it instead. An over-long factor 2 truncates on the side away from the alignment.

Numeric operands follow the same positional rule over the field's digits, and both operands' decimal positions are ignored: moving 1.00 into a three-position field with one decimal gives 10.0.

A date, time or timestamp on either side converts first and then moves. Factor 1 carries the format of the character or numeric operand, never of the date field, and must be blank when both sides are date/time types:

     C     *MDY          MOVE      chrDate       dateFld

Date-to-time and time-to-date are refused — go through a timestamp.

Because MOVE, MOVEL, GOTO and TAG are now lexer keywords, free-format source can no longer use them as variable names.

COMP sets its three resulting indicators — high, low and equal — in positions 71-72, 73-74 and 75-76. It is the only opcode for which those columns may be non-blank.

CASxx and CABxx glue a comparison mnemonic (EQ NE LT LE GT GE, or nothing for the unconditional form) onto the opcode. CABxx is a comparison guarding a branch. CASxx lines chain like SELECT/WHEN — the first true comparison runs its subroutine and the rest are skipped — and the group is closed by ENDCS.

Both take the branch target — a label for CABxx, a subroutine name for CASxx — in the Result field, with the two values being compared in Factor 1 and Factor 2. The unconditional CAS/CAB forms leave both factors blank.

     C     amt           CABGT     limit         OVER
     C     code          CASEQ     'A'           SUBA
     C     code          CASEQ     'B'           SUBB
     C                   CAS                     SUBOTHER
     C                   ENDCS

Traditional Program Calls

CALL with its PARM lines names the program in Factor 2:

     C                   CALL      'ORD100'
     C                   PARM                    custNo
     C                   PARM                    total

There is no prototype, so the callee's signature is synthesized from the PARM operands' declared types, every parameter passed by reference. If caller and callee disagree, the link fails — an ugly error, but not a silent one.

A named PLIST can appear anywhere in the source, including after the calls that use it:

     C                   CALL      'ORD100'      ORDPARMS
     C     ORDPARMS      PLIST
     C                   PARM                    custNo
     C                   PARM                    total

The one ordering rule is that the PLIST must be in the same run of C-spec lines as its CALL or a later one — a CALL in an earlier run than its PLIST is refused by name.

PARM also supports Factor 1 and Factor 2 operands, which move data into the parameter before the call and out of it afterwards.

Program Parameters — *ENTRY PLIST

A member with an *ENTRY PLIST does not compile to a main(). It becomes a callable function taking each parameter by reference, which is exactly the signature a caller's CALL/PARM synthesizes:

     C     *ENTRY        PLIST
     C                   PARM                    inCustNo
     C                   PARM                    outTotal

The function's name comes from the source file name, upper-cased with the directory and extension stripped — ORD100.rpgle is reachable as CALL 'ORD100'. A file name that is not a usable symbol is refused with a message telling you to rename the file.

/COPY and /INCLUDE

Both work outside /free blocks, as a line-splicing pass that runs before anything else, so a copied H/F/D/C line is indistinguishable from one that was physically present:

      /COPY custdefs.rpgleinc

The text after the directive is a literal filename opened relative to the current working directory — there is no library or member catalog and no search path. Nesting is capped at 10 levels. Line numbers after an expansion point refer to the flattened line stream rather than the original file.

Embedded SQL in Fixed Columns

C/EXEC SQL opens the statement, C+ continues it, and C/END-EXEC closes it:

     C/EXEC SQL
     C+ SELECT COUNT(*) INTO :rowCount
     C+   FROM customers WHERE status = 'A'
     C/END-EXEC

The gathered text goes to the same SQL handling free-format EXEC SQL uses, so everything in the Embedded SQL chapter applies unchanged.

What Is Not Supported

The RPG cycle is not implemented and will not be. Concretely, in fixed-format source that means:

Rejected Where Note
Control levels L0, L1-L9, LR C-spec positions 7-8 Cycle-only
Resulting indicators C-spec 71-76 Except on COMP
Inline field length/decimals C-spec 64-70 Use a D-spec
P-specs — No procedure declaration; use a /free block
D-spec types C, PR, PI D-spec 24-25 Constants and prototypes; use a /free block
DO — Use FOR
Matching fields I-spec 65-66 Cycle-adjacent
Sequence checking I-spec 17-20 Must be blank

Every one of these produces a distinct compile error naming the problem, rather than being silently ignored.

Clone this wiki locally