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
PGMis 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
PARMstill needs its ownDCLcommand, declared with a type and length.PGMonly names the parameter list;DCLsupplies the attributes. - Up to 255 variables can be passed in
PARM. - A variable used as a
PARMcannot also carry aVALUEon itsDCL. 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
*Nfor an omitted optional parameter only when the caller supports omitted parameters, whichPGM/PARMvariables 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.
| Parameter | Purpose | Notes |
|---|---|---|
VAR | The variable name | Must begin with &. Required, positional 1 |
TYPE | *CHAR, *DEC, *LGL, *INT, *UINT, or *PTR | Required, positional 2 |
LEN | Length, and decimal positions for *DEC | Optional, 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 |
VALUE | Initial value | Optional, positional 4. Cannot be used with *PTR, *BASED, or *DEFINED storage, and cannot be used on a variable that is also in PGM PARM |
STG | Storage class: *AUTO (default), *BASED, *DEFINED | Rarely 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:
| Parameter | Default | Notes |
|---|---|---|
SRCFILE | QCLSRC | Library defaults to *LIBL |
SRCMBR | *PGM | Member named the same as the program, if omitted |
DFTACTGRP | *YES | Program runs in the default activation group |
ACTGRP | *STGMDL | Ignored while DFTACTGRP(*YES) applies; ACTGRP cannot be specified at all with DFTACTGRP(*YES) |
REPLACE | *NO | Set *YES to recompile over an existing program of the same name |
DBGVIEW | *STMT | Use *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; aDCLappearing after, say, aCHGVARis a syntax error. - Giving a
PGM PARMvariable aVALUE. 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
DCLorder after the declarations block is fixed. Declarations can be reordered among themselves, but none can follow the first executable statement.
Summary
PGMopens the program and optionally lists parameters inPARM; if used, it must be the first line.- Every variable, including each
PGM PARMvariable, needs its ownDCLwithVARandTYPEat minimum. - All
DCL/DCLFcommands must sit betweenPGMand the first executable command. ENDPGMcloses the source; both it andPGMare technically optional but are standard practice.CRTBNDCLis the normal ILE compile command;CRTCLPGMis 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.