Supported RPG Features

Triton RPG supports the /COPY and /INCLUDE compiler directives in both fixed-format and free-format source. The member's text is included exactly as written, matching IBM i behavior.

Both directives are functionally identical in non-SQL contexts. The directive keyword is case-insensitive (/COPY, /copy, /Copy are all recognized).

Column 6 on a fixed-format directive. The directive is recognized by the / in column 7; column 6 may hold anything and is ignored. Writing the spec letter there is common and works as written - the two forms below are equivalent, and the same applies to /INCLUDE and to the conditional directives (/DEFINE, /UNDEFINE, /IF, /ELSEIF, /ELSE, /ENDIF):

      /COPY MYFIELDS
     D/COPY MYFIELDS
     H/DEFINE FEATURE
     D/IF DEFINED(FEATURE)

Syntax forms:

Form Example Description
Absolute path /COPY /home/rpg/includes/MYVAR.rpgle IFS-style absolute path
Relative path /COPY includes/MYVAR.rpgle Relative to source dir, CWD, or include paths
Quoted path /COPY 'path with spaces.rpgle' Single or double quotes (forces IFS interpretation)
Bare name /COPY MYFIELDS Searches source dir and include paths
file,member /COPY QRPGLESRC,MYCOPY Searches for QRPGLESRC/MYCOPY
library/file,member /COPY MYLIB/QRPGLESRC,MYCOPY Searches for MYLIB/QRPGLESRC/MYCOPY

Member resolution:

For bare names and relative paths, Triton RPG searches in this order:

  1. The source file's directory
  2. The working directory (the directory from which rpgc is invoked)
  3. Each directory specified via --include-path (in order given)

Quoted paths containing a directory separator (e.g., /COPY 'includes/myvar.rpgleinc') are treated as IFS-style relative paths and follow this same search order.

If the specified path has no file extension, Triton RPG tries the name as-is, then appends each RPG source extension in this order:

  1. .rpgleinc
  2. .sqlrpgleinc
  3. .rpgle
  4. .sqlrpgle
  5. .rpg

The include-only extensions come first, so a directory holding both MYFIELDS.rpgleinc and MYFIELDS.rpg resolves the file written to be included. A file named exactly as written beats every extension. File name matching is case-insensitive, over the same extension list.

Cross-format inclusion:

Each copy member has its own format context: a copy member is fixed-format by default unless its first non-blank line is **FREE.

A fixed-format member included into **FREE source keeps that format and is compiled as written. Nothing is translated, so there is no set of "convertible" constructs - anything valid in a column-limited program is valid in the member, including constructs that have no free-form spelling at all. This is what makes the escape hatch for fully-free source work: a TAG (with the GOTO or CABxx that reaches it), an I-spec, or an O-spec cannot be written directly in a **FREE member, but can be reached by /COPY:

// TAGMBR - a fixed-format copy member
     C     ERR           TAG
     C                   EVAL      X = 99
**FREE
dcl-s x packed(3:0) inz(0);
x = 1;
/copy TAGMBR
dsply x;      // 99 - the copied calculation runs where the /COPY sits
*inlr = *on;

The member's statements execute in position, between the free-format statements that surround the /COPY. Declarations from the member are visible to the free main, and a data structure or prototype keeps its grouping - subfields stay members of the structure rather than becoming unrelated standalone fields. The EXEC SQL WHENEVER SQLERROR GOTO form is usable this way too: the free-format SQL statement branches to a TAG in a copied member.

This is the only route for a fixed-form construct into a **FREE member. **FREE source itself is free-format throughout and cannot contain fixed-format specs.

The other direction - a **FREE copy member included into a fixed-format source - is converted, because a free-form declaration does have a faithful fixed-format rendering. DCL-S declarations and DCL-DS ... END-DS data structures are accepted, and the structure and each field's type are preserved, so the structure is preserved rather than dropped. Field data types carry across (a numeric, pointer, graphic, UCS-2, date, or indicator field keeps its type - it is never silently turned into character), and a varying string type keeps its varying attribute: VARCHAR, VARGRAPH, or VARUCS2 becomes the corresponding fixed-format data type (A, G, or C) with the VARYING keyword rather than collapsing to a fixed-length field. A field whose type has no fixed-format equivalent is rejected with error TRN0007 rather than converted incorrectly, and a free-form statement with no fixed-format spec rendering (a DCL-C constant or a free-form calculation) is rejected with error TRN0010 naming the statement and member - never silently dropped. Restate such a copy member using DCL-S declarations and DCL-DS data structures, or keep it in the same format as the including source.

Compiler directives inside a copy member are honored in both directions. A nested /COPY (or /INCLUDE) in a converted member is still expanded, and a conditional-compilation block (/IF, /ELSEIF, /ELSE, /ENDIF, /DEFINE, /UNDEFINE, /EOF) inside it is still evaluated - so a **FREE source can /COPY a fixed member that itself /COPYs further members and guards declarations with /IF DEFINED(...), and the fixed-including-**FREE case behaves the same.

Nesting:

Copy members may themselves contain /COPY directives, up to a maximum depth of 32. Circular inclusion is detected and reported as error TRN0006.

CLI usage:

# Compile with include paths for /COPY member resolution
rpgc --include-path /home/rpg/includes --include-path /shared/copybooks MYPGM.rpgle

# Multiple include paths are searched in order
rpgc --include-path ./includes --include-path ../shared -o out MYPGM.rpgle