CL Program Structure: PGM, DCL, and ENDPGM

Every CL program, no matter how large, has the same three-part skeleton: PGM at the top, one DCL command for each variable, and ENDPGM at the bottom. This page walks through that skeleton piece by piece and compiles a minimal example. For a wider tour of what CL does day to day, see CL Programming Explained. For variable types and CHGVAR in depth, see CL Variables, Data Types, and CHGVAR.

The Skeleton

PGM
  DCL VAR(&MSG) TYPE(*CHAR) LEN(50)

  CHGVAR VAR(&MSG) VALUE('Hello from CL')
  SNDPGMMSG MSG(&MSG) TOPGMQ(*EXT)

ENDPGM

Verification level: IBM DOC CHECKED. This source was checked against the IBM i 7.5 CL command reference below; it has not been compiled or run.

PGM marks the start of the program and, if the program takes no parameters, can be omitted entirely — the compiler assumes an empty one if the source doesn’t start with it. DCL commands declare every variable the program uses. ENDPGM marks the end; if the compiler reaches the last statement without one, it assumes one there too. Both commands are technically optional for this reason, but writing them explicitly is the near-universal convention and makes the program’s boundaries unambiguous to a reader.

PGM and the PARM Parameter

PGM has one parameter, PARM, which lists the CL variables that receive values from whatever calls the program:

PGM PARM(&CUSTNO &STATUS)
  DCL VAR(&CUSTNO) TYPE(*CHAR) LEN(8)
  DCL VAR(&STATUS) TYPE(*CHAR) LEN(1)
  ...
ENDPGM

Verification level: IBM DOC CHECKED.

Rules worth knowing, per the IBM i 7.5 CL reference:

  • If PGM is used, it must be the very first command in the source. Nothing — not even a comment-only line that the compiler treats as a statement — can precede it.
  • Every variable named in PARM still needs its own DCL command, declared with a type and length. PGM only names the parameter list; DCL supplies the attributes.
  • Up to 255 variables can be passed in PARM.
  • A variable used as a PARM cannot also carry a VALUE on its DCL. The value it holds comes from the caller, not from an initializer in the program.
  • ILE does not check for parameter mismatches between caller and called program. If the caller passes fewer arguments than the called program declares, or the types disagree, the result is unpredictable — this is a common source of bugs when a CL program’s signature changes but a caller isn’t updated to match.
  • A called CL program can accept *N for an omitted optional parameter only when the caller supports omitted parameters, which PGM/PARM variables cannot receive directly as null.

Parameters are passed by reference. If the called program changes a parameter’s value with CHGVAR, the caller sees the new value after CALL returns — there is no separate “by value” passing mode in classic CL parameter passing.

DCL: Declaring Every Variable

DCL declares one variable per command. The minimum useful form is:

DCL VAR(&NAME) TYPE(*CHAR) LEN(30)

Verification level: IBM DOC CHECKED.

ParameterPurposeNotes
VARThe variable nameMust begin with &. Required, positional 1
TYPE*CHAR, *DEC, *LGL, *INT, *UINT, or *PTRRequired, positional 2
LENLength, and decimal positions for *DECOptional, positional 3. Omit it and the default for the type applies (32 bytes for *CHAR, 15 digits/5 decimals for *DEC) unless VALUE sets an implicit length
VALUEInitial valueOptional, positional 4. Cannot be used with *PTR, *BASED, or *DEFINED storage, and cannot be used on a variable that is also in PGM PARM
STGStorage class: *AUTO (default), *BASED, *DEFINEDRarely needed for a first program

Placement rule: every DCL (and DCLF) must come immediately after PGM and before the first executable command. They can appear in any order relative to each other, but once an executable command such as CHGVAR or CALL appears, no further DCL is allowed.

A full declaration with an initial value:

PGM
  DCL VAR(&COUNT)  TYPE(*DEC) LEN(5 0) VALUE(0)
  DCL VAR(&OK)     TYPE(*LGL) VALUE('1')
  DCL VAR(&CUSTNO) TYPE(*CHAR) LEN(8)
ENDPGM

Verification level: IBM DOC CHECKED.

If VALUE is omitted, *CHAR variables start as blanks, numeric types start at zero, and *LGL starts as '0'. See CL Variables, Data Types, and CHGVAR for the full type reference, arithmetic rules, and common CHGVAR mistakes.

ENDPGM

ENDPGM takes no parameters and simply marks the end of the source:

ENDPGM

Functionally it behaves like RETURN placed at the very end — control passes back to the command immediately after CALL in whatever called the program. Because it’s optional, some shops omit it for very short programs, but including it is good practice: it gives a reader (and any tooling that scans source members) an unambiguous end-of-program marker, and it’s required once anything — comments, blank member padding — appears after what would otherwise be the last real statement.

Compiling the Source

CL source lives in a source physical file member of type CLLE (or CLP for the older OPM style) and is compiled with one of two commands:

CRTBNDCL PGM(MYLIB/HELLOCL) SRCFILE(MYLIB/QCLSRC) SRCMBR(HELLOCL)

Verification level: IBM DOC CHECKED.

CRTBNDCL (Create Bound CL Program) compiles a single source member directly into a *PGM object in one ILE step — the equivalent of CRTBNDRPG for RPGLE. Key defaults from the IBM i 7.5 CL reference:

ParameterDefaultNotes
SRCFILEQCLSRCLibrary defaults to *LIBL
SRCMBR*PGMMember named the same as the program, if omitted
DFTACTGRP*YESProgram runs in the default activation group
ACTGRP*STGMDLIgnored while DFTACTGRP(*YES) applies; ACTGRP cannot be specified at all with DFTACTGRP(*YES)
REPLACE*NOSet *YES to recompile over an existing program of the same name
DBGVIEW*STMTUse *SOURCE for source-level debugging

The older CRTCLPGM (Create CL Program) is the OPM equivalent — it also compiles a single source member into a *PGM, but the resulting program is an original-program-model object rather than an ILE bound program, and it defaults REPLACE to *YES rather than *NO. For new work, CRTBNDCL is the normal choice; CRTCLPGM mainly exists for compatibility with older CL source.

CRTCLPGM PGM(MYLIB/HELLOCL) SRCFILE(MYLIB/QCLSRC) SRCMBR(HELLOCL)

Verification level: IBM DOC CHECKED.

After either compile succeeds, run the program the same way regardless of which command built it:

CALL PGM(MYLIB/HELLOCL)

Common Mistakes

  • Putting an executable command before a DCL. The compiler requires all declarations up front; a DCL appearing after, say, a CHGVAR is a syntax error.
  • Giving a PGM PARM variable a VALUE. It’s disallowed — the value comes from the caller.
  • Mismatched parameter lists between caller and called program. Because ILE does not validate this at call time, a stale caller and a changed callee can run without any compile or early runtime error and still produce wrong results.
  • Forgetting that DCL order after the declarations block is fixed. Declarations can be reordered among themselves, but none can follow the first executable statement.

Summary

  • PGM opens the program and optionally lists parameters in PARM; if used, it must be the first line.
  • Every variable, including each PGM PARM variable, needs its own DCL with VAR and TYPE at minimum.
  • All DCL/DCLF commands must sit between PGM and the first executable command.
  • ENDPGM closes the source; both it and PGM are technically optional but are standard practice.
  • CRTBNDCL is the normal ILE compile command; CRTCLPGM is its OPM-era counterpart.

Sources: IBM i 7.5 PGM command, IBM i 7.5 DCL command, IBM i 7.5 ENDPGM command, IBM i 7.5 CRTBNDCL command, and IBM i 7.5 CRTCLPGM command. All examples above are documentation-checked only; no compiler or runtime access was used to verify them.

Continue with CL Variables, Data Types, and CHGVAR for a full look at variable types, or Reading Database Files in CL with DCLF and RCVF to add file I/O to a program.

Leave a Comment

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

Scroll to Top