Supported RPG Features

A fixed-format D-spec data structure groups one or more subfield D-spec lines under a DS header line. The header carries the DS name in columns 7-21 with DS in the def-type field (columns 24-25); the subfield lines that follow have a blank def-type and describe the layout (length, data type, decimals, keywords) the same way standalone D-specs do.

When the data-type column (column 40) is left blank, the type is inferred from the decimal-positions column (columns 41-42). A blank data type with decimal positions is numeric, and the default numeric type depends on where the field is defined: a DS subfield defaults to zoned (so 6 0 is 6S 0 and 9 2 is 9S 2), while a standalone field or prototype parameter defaults to packed (so 6 0 is 6P 0 and 9 2 is 9P 2). This holds whether the subfield is written in length notation or with absolute FROM/TO byte positions. A blank data type without decimal positions is character in every case.

The DS terminates implicitly when:

Example: a 6-byte character buffer whose first two bytes are also addressable as a 2-byte slice via the OVERLAY keyword.

     D DateDS          DS
     D DateChr                        6A
     D MonChr                         2A   OVERLAY(DateChr)
     D DayChr                         2A   OVERLAY(DateChr:3)

OVERLAY Keyword

OVERLAY(parent) aligns the subfield with the first byte of parent's storage; OVERLAY(parent:N) starts at byte N (1-indexed) within the parent. Reads and writes through any overlaid subfield are visible through the parent and every sibling overlay. Subfields without OVERLAY are laid out sequentially in declaration order and occupy their own bytes.

Subfields that overlap - through OVERLAY, POS(n), or from/to positions - share storage: a write through one is visible through every other view of those bytes. Subfields that do not overlap occupy their own bytes.

OVERLAY(parent:*NEXT) positions the subfield at the next available byte within the parent - one byte past the highest position used so far by the subfields overlaying that parent. Because it tracks the highest position used by all prior overlays of the parent (not merely the immediately preceding subfield), an earlier-but-lower overlay declared in between does not pull the next *NEXT subfield back. This lets a run of overlaid subfields be packed end-to-end without hard-coding byte positions.

Character Subfields Overlaying Numeric Ones

A data structure's storage is a single byte buffer under one code page - the job's, or the field's own where a CCSID keyword names one. A character subfield laid over a numeric one therefore reads that numeric's storage image directly:

Numeric subfield Storage Read through a CHAR overlay
ZONED(5:0) = 12345 x'F1F2F3F4F5' 12345
ZONED(5:0) = -12345 x'F1F2F3F4D5' 1234N - the sign is overpunched into the last byte's zone, giving the J - R letter for that digit; the field is still five bytes
PACKED(5:2) = 3.14 x'00314F' the characters those BCD bytes happen to be - not meaningful as text, but stable

Overlaying a character subfield to inspect or move a numeric's bytes therefore works as written. Note the packed row: a program doing this on packed data is reading BCD, so treat the result as bytes rather than text.

Assigning one character subfield to another is a byte move, so every byte survives - including two the code page cannot tell apart as text. Under IBM037 and IBM500, bytes x'15' and x'25' both map to the same character, so a move routed through text would turn one into the other; a packed field holding 250.00 stores x'25000F', so that byte occurs in ordinary data. Moving a character overlay of such a field to another field returns 250.00, matching IBM i, where a character field is bytes and has no intermediate text form.

The byte move applies to a plain subfield-to-subfield assignment. An assignment whose source is an expression - a concatenation, a %SUBST, a BIF result - genuinely produces text and is encoded through the code page, which is the same thing IBM i does when a program builds a value rather than moving one. The practical consequence is that those two byte values are not distinguishable once a value has passed through a text operation.

SAMEPOS Keyword

SAMEPOS(subfield) positions the subfield at the same starting byte offset as the referenced subfield. It is the modern, name-based alternative to OVERLAY. Unlike OVERLAY, the SAMEPOS subfield does not need to fit within the referenced subfield. The referenced subfield must be previously defined in the same data structure.

DCL-DS MyDs;
  FullName  CHAR(10);
  FirstPart CHAR(5) SAMEPOS(FullName);
END-DS;

Positional Notation (FROM/TO byte positions)

A subfield may instead be defined by absolute byte positions: the FROM column (positions 26-32) gives the starting byte and the TO column (positions 33-39) gives the ending byte, so the subfield occupies TO - FROM + 1 bytes of the structure. Subfields whose byte ranges overlap share storage, exactly like OVERLAY. This is most often used to carve fixed fields out of a buffer:

     D rec             DS
     D  FromBuf                1     20A
     D  RecCode                1      3A
     D  RecName                4     13A
     D  RecQty                14     20A

Here RecCode is bytes 1-3, RecName bytes 4-13 (10 bytes), and RecQty bytes 14-20 (7 bytes), all overlaying the 20-byte FromBuf. For numeric types the byte span maps to the type's digit count: a packed span is 2n-1 digits (e.g. a 5-byte span is 9 digits), and a binary (B) span is 4 digits for 2 bytes or 9 digits for 4 bytes. Positional subfields overlap by definition, so a write through one is visible through the others.

The PACKEVEN keyword on a packed subfield changes the digit count from the default odd formula (2n-1) to the even formula (2n-2). For example, a 3-byte packed span is 5 digits by default, but 4 digits with PACKEVEN.

OCCURS Keyword

OCCURS(n) on a DS header declares a multiple-occurrence data structure with n independent copies of the subfield layout. Subfields are accessed by name - the current occurrence determines which copy is read or written.

Fixed-format:

     D DS1             DS                  OCCURS(3)
     D  FLD1                          5  0
     D  FLD2                         10A

Free-format:

DCL-DS myds OCCURS(3);
  fld1 PACKED(5:0);
  fld2 CHAR(10);
END-DS;

Switching occurrences:

In free-format, use the %OCCUR BIF to get or set the active occurrence (1-based):

%OCCUR(myds) = 2;       // set occurrence to 2
curOc = %OCCUR(myds);   // get current occurrence

In fixed-format, use the OCCUR opcode (C-spec):

Field Content
Factor 1 Occurrence to set (optional; 1-based numeric)
Opcode OCCUR
Factor 2 Multiple-occurrence DS name
Result Variable to receive current occurrence (optional)
Cols 73 - 74 ER indicator (optional; set on out-of-range)

At least one of Factor 1 or Result must be specified. The default occurrence is 1. Out-of-range values (< 1 or > OCCURS count) raise status 00122; handle with the (E) extender or an ER indicator.

     C     2             OCCUR     DS1
     C                   Z-ADD     100           FLD1
     C                   OCCUR     DS1           CUROC

Clearing OCCURS data structures:

CLEAR on an OCCURS data structure clears only the current occurrence by default. Each subfield is set to its type default (numeric → 0, alpha → blanks, indicator → *OFF). The occurrence level is unchanged.

With the *ALL qualifier, all occurrences are cleared and the occurrence level is reset to 1.

Free-format:

CLEAR DS1;           // clear current occurrence only
CLEAR *ALL DS1;      // clear all occurrences, reset to occurrence 1

Fixed-format (*ALL goes in Factor 2, cols 36 - 49):

     C                   CLEAR                   DS1
     C                   CLEAR     *ALL          DS1

ALIGN Keyword

ALIGN on a DS header places integer, unsigned and float subfields on their natural byte boundaries, inserting padding before a subfield when necessary so its storage begins on that boundary:

Subfield Boundary
INT(5) / UNS(5) (2 bytes) 2
INT(10) / UNS(10) (4 bytes), FLOAT(4) 4
INT(20) / UNS(20) (8 bytes), FLOAT(8) 8

Every other subfield type stays packed against the preceding one. A pointer subfield is always 16-byte aligned, with or without ALIGN. The padding counts toward %SIZE: a CHAR(3) followed by an aligned INT(10) sizes 8 (the integer starts at offset 4), not 7.

Free-format and fixed-format both honor the keyword:

DCL-DS aligned QUALIFIED ALIGN;
  a CHAR(3);
  n INT(10);        // placed at offset 4
END-DS;
     D aligned        DS                  QUALIFIED ALIGN
     D  a                            3A
     D  n                           10I 0

Plain ALIGN aligns the subfields but does not round the structure up. ALIGN(*FULL) additionally pads the whole structure up to a multiple of its strictest member alignment, so an array element's stride equals %SIZE - an INT(10) followed by a CHAR(1) sizes 5 under ALIGN but 8 under ALIGN(*FULL). Use ALIGN(*FULL) when the structure maps to a non-packed C structure or is used as a DIM array whose stride must match a C layout.