Supported RPG Features

When a procedure has both a prototype (DCL-PR) and an interface (DCL-PI), the interface must match the prototype, position by position: the same number of parameters, the same parameter types, the same passing mode (by reference, VALUE, or CONST), and the same OPTIONS(...) flags. A type-count mismatch is reported as a compile error; a passing-mode mismatch is reported as TRN3008 and an OPTIONS mismatch as TRN3009. Parameters are compared positionally - the PR and PI parameter names need not be the same, only their declared attributes.

Every prototyped call is also checked for argument count. The call must pass all parameters except trailing OPTIONS(*NOPASS) ones: passing fewer arguments than the number of mandatory parameters is reported as TRN3011, and passing more arguments than the prototype declares is reported as TRN3012. Arguments are matched to parameters positionally.

OPTIONS(*NOPASS) parameters must form a trailing run: once a parameter specifies OPTIONS(*NOPASS), every parameter after it in the same prototype or interface must also specify OPTIONS(*NOPASS). A non-*NOPASS parameter following a *NOPASS one is reported as TRN3013.

A parameter declared OPTIONS(*NOPASS) may be omitted by the caller (along with every parameter after it). Inside the procedure, %PARMS returns the number of arguments the caller actually passed - not the declared maximum - so the body can guard reads of optional parameters:

DCL-PR sub;
  a PACKED(5:0) CONST;
  b PACKED(5:0) CONST OPTIONS(*NOPASS);
END-PR;

sub(10);                             // b omitted

DCL-PROC sub;
  DCL-PI *N;
    a PACKED(5:0) CONST;
    b PACKED(5:0) CONST OPTIONS(*NOPASS);
  END-PI;
  DSPLY ('P=' + %CHAR(%PARMS));      // P=1
END-PROC;

The same rule applies to a main program's entry interface (*ENTRY / the top-level DCL-PI). There, %PARMS returns the number of parameters actually passed on the call that activated the program - the equivalent of the PSDS *PARMS field - which may be fewer than the declared count when trailing OPTIONS(*NOPASS) parameters are omitted. A program with a three-parameter entry interface (two OPTIONS(*NOPASS)) called with one argument sees %PARMS = 1; called with three, %PARMS = 3. (A program instantiated and run directly, rather than through an RPG EXTPGM call, has no caller-supplied count and so reports the declared parameter count.)

%PASSED(paramName) returns *ON if the named OPTIONS(*NOPASS) parameter was actually passed by the caller on this invocation, and *OFF otherwise. It is the recommended alternative to comparing %PARMS >= %PARMNUM(param):

IF %PASSED(b);
  DSPLY 'b was passed';
ENDIF;

A parameter declared OPTIONS(*OMIT) may be passed *OMIT by the caller. Inside the procedure, %ADDR of an omitted parameter compares equal to *NULL (and %OMITTED returns *ON), so the body can detect the omission:

DCL-PR sub;
  a PACKED(5:0) CONST;
  b PACKED(5:0) OPTIONS(*OMIT);
END-PR;

sub(10 : *OMIT);                     // b omitted

DCL-PROC sub;
  DCL-PI *N;
    a PACKED(5:0) CONST;
    b PACKED(5:0) OPTIONS(*OMIT);
  END-PI;
  IF %ADDR(b) = *NULL;
    DSPLY 'OMITTED';
  ENDIF;
END-PROC;

A character parameter declared OPTIONS(*VARSIZE) accepts a character argument shorter than the parameter's declared length. Inside the procedure the parameter is still seen at its declared length - %LEN(s) returns the prototype length, not the length of the argument that was passed, and a shorter argument is blank-padded to the declared width. The procedure cannot learn the actual argument length from %LEN; pass the length as a separate parameter when the callee needs it:

DCL-PR sub;
  s CHAR(20) OPTIONS(*VARSIZE);
END-PR;

DCL-S short CHAR(5);
short = 'ABCDE';
sub(short);                          // a CHAR(5) argument into a CHAR(20) param

DCL-PROC sub;
  DCL-PI *N;
    s CHAR(20) OPTIONS(*VARSIZE);
  END-PI;
  DSPLY ('V=' + %SUBST(s : 1 : 5));  // V=ABCDE  (the bytes the argument provided)
  DSPLY ('L=' + %CHAR(%LEN(s)));     // L=20     (the declared length, not 5)
END-PROC;

OPDESC and CEEDOD

When a prototype or interface specifies the OPDESC keyword, the caller passes operational descriptor metadata alongside each argument. The callee can query this metadata - specifically the actual length, descriptor type, and data type - via the ILE bindable API CEEDOD:

DCL-PR showLen OPDESC;
  str CHAR(50) CONST OPTIONS(*VARSIZE);
END-PR;

showLen('Hello');

DCL-PROC showLen;
  DCL-PI *N OPDESC;
    str CHAR(50) CONST OPTIONS(*VARSIZE);
  END-PI;

  DCL-PR CEEDOD EXTPROC('CEEDOD');
    parmNum  INT(10) CONST;
    descType INT(10);
    dataType INT(10);
    descInfo1 INT(10);
    descInfo2 INT(10);
    dataLen  INT(10);
    fc       CHAR(12) OPTIONS(*OMIT);
  END-PR;

  DCL-S descType INT(10);
  DCL-S dataType INT(10);
  DCL-S descInfo1 INT(10);
  DCL-S descInfo2 INT(10);
  DCL-S dataLen INT(10);

  CEEDOD(1 : descType : dataType : descInfo1 : descInfo2 : dataLen : *OMIT);
  // dataLen = 5 (actual argument length, not the prototype's 50)
END-PROC;

OPDESC is supported on both DCL-PR and DCL-PI in free-format and fixed-format. In fixed-format C-specs, CALLB(D) is the equivalent of OPDESC on a prototype - the (D) operation extender passes operational descriptors for the PARM variables, producing the same descriptor metadata that a prototyped OPDESC call does. CEEDOD returns:

Parameter Meaning
descType Descriptor type (2 = element)
dataType Data type (2 = character)
descInfo1 CCSID (0 = job default)
descInfo2 Reserved (0)
dataLen Actual byte length of the passed argument

A character parameter passed by CONST or VALUE may be declared OPTIONS(*TRIM) or OPTIONS(*RIGHTADJ). *TRIM is valid on both fixed-length and varying-length (VARCHAR) character parameters; *RIGHTADJ applies to a fixed-length parameter. The transformation is applied to the argument at the call site before it is passed:

DCL-PR showTrim;
  p CHAR(10) CONST OPTIONS(*TRIM);
END-PR;
DCL-PR showRadj;
  p CHAR(10) CONST OPTIONS(*RIGHTADJ);
END-PR;

showTrim('  hi  ');                  // procedure receives 'hi' (blanks removed)
showRadj('hi');                      // procedure receives '        hi'

*TRIM applies to fixed- or varying-length character parameters passed by CONST or VALUE (and to an OPTIONS(*STRING) pointer); *RIGHTADJ applies to non-varying character parameters passed the same way. Any other kind of parameter is a compile-time error (TRN3005 for *TRIM, TRN3006 for *RIGHTADJ). They combine with the other options via colons, e.g. OPTIONS(*NOPASS:*TRIM).

OPTIONS(*CONVERT)

A non-varying character parameter passed by CONST or VALUE may be declared OPTIONS(*CONVERT). The call site then accepts an argument of any type - the value is converted to character exactly as %CHAR renders it, and then assigned to the parameter, blank-padded to its declared length.

DCL-PR show;
  value CHAR(30) CONST OPTIONS(*CONVERT);
END-PR;

DCL-S d DATE INZ(D'2026-03-17');
DCL-S amount PACKED(7:2) INZ(-1234.56);

show(d);                             // procedure receives '2026-03-17'
show(amount);                        // procedure receives '-1234.56'
show(12345);                         // procedure receives '12345'

What the procedure receives, by argument kind:

Argument Received
DATE ISO form, e.g. 2026-03-17
TIME ISO form, e.g. 14.05.09
TIMESTAMP 2026-03-17-14.05.09.123456 - all six fractional digits
PACKED / ZONED no leading zeros, no leading blank, decimal point at the declared scale, minus sign only when negative
INT / UNS the digits, with a minus sign only when negative
character unchanged

Every result is left-justified and blank-padded to the parameter's declared length. Without OPTIONS(*CONVERT), passing a non-character argument to a character parameter is a compile-time error (TRN3010).

The option combines with the others via colons, e.g. OPTIONS(*CONVERT:*NOPASS).

OPTIONS(*STRING)

A basing-pointer parameter passed by CONST or VALUE may be declared OPTIONS(*STRING). The call site then accepts a character argument and passes the address of a null-terminated temporary copy of it. The procedure reads the value back with %STR.

The temporary is terminated from the character value as passed. A fixed-length CHAR argument therefore contributes its full blank-padded width, while a VARCHAR contributes only its current length:

DCL-PR showStr VARCHAR(30);
  p POINTER VALUE OPTIONS(*STRING);
END-PR;

DCL-S fld  CHAR(10)    INZ('ABC');
DCL-S vfld VARCHAR(10) INZ('XY');

showStr(fld);      // %STR(p) is 'ABC       ' - length 10, blanks kept
showStr(vfld);     // %STR(p) is 'XY' - length 2
showStr('LIT');    // %STR(p) is 'LIT' - length 3

Combine it with *TRIM to strip leading and trailing blanks before the value is terminated:

DCL-PR showTrim VARCHAR(30);
  p POINTER VALUE OPTIONS(*STRING:*TRIM);
END-PR;

showTrim(fld);     // %STR(p) is 'ABC' - length 3

Passing an argument that is already a pointer (a pointer variable, %ADDR(...), or *NULL) passes that pointer through unchanged - no temporary is built.

The parameter must be a basing pointer passed by CONST or VALUE; anything else is a compile-time error (TRN3007). The option is available in both fixed-format D-specs and free-format DCL-PR/DCL-PI.