Programming on IBM i: sources, ILE, RPG and SQL Chapter 4 of 9

CL: the language that holds the rest together

Reviewed on Verified on IBM i 7.5

Log in to save

CL (Control Language) is the language the system's commands are written in, and it is also a real programming language, with variables, conditions, loops and error handling.

In practice it is what holds everything else together: it prepares the environment, calls the programs, handles exceptions, submits the work. If something in an application runs at night, there is almost certainly a CL launching it.

A minimal CL

PGM        PARM(&COMPANY)
  DCL      VAR(&COMPANY) TYPE(*CHAR) LEN(3)
  DCL      VAR(&DATE)    TYPE(*CHAR) LEN(8)
  DCL      VAR(&MSG)     TYPE(*CHAR) LEN(132)

  RTVSYSVAL SYSVAL(QDATE) RTNVAR(&DATE)

  OVRDBF   FILE(ORDERS) TOFILE(DATALIB/ORDERS&COMPANY)

  CALL     PGM(PRODLIB/CLOSE) PARM(&COMPANY)
  MONMSG   MSGID(CPF0000) EXEC(DO)
    CHGVAR VAR(&MSG) VALUE('Close failed for company' *BCAT &COMPANY)
    SNDPGMMSG MSG(&MSG) TOMSGQ(QSYSOPR) MSGTYPE(*INFO)
    DLTOVR FILE(ORDERS)
    RETURN
  ENDDO

  DLTOVR   FILE(ORDERS)
ENDPGM

Nearly everything a CL actually does is in there: taking parameters, reading system state, redirecting a file, calling a program, noticing it went wrong and telling somebody.

MONMSG, which is the heart of it

A command that fails sends an exception message. MONMSG is the only way to notice: without it the CL stops with an error which, in batch, nobody sees until the next day.

It is used in two ways, and the difference matters:

CALL PGM(PRODLIB/CLOSE)
MONMSG MSGID(CPF0000) EXEC(GOTO CMDLBL(FAILED))

Placed right after a command, it applies to that command only.

PGM
  MONMSG MSGID(CPF0000 MCH0000) EXEC(GOTO CMDLBL(FAILED))
  ...

Placed at the start of the program, right after the declarations (or after PGM if there are none), it applies to every command: the program-level MONMSG. There EXEC can only hold a GOTO to a label, and a MONMSG on a single command takes precedence over the general one.

Warning

MONMSG MSGID(CPF0000) catches any message in the CPF family, and the way it is most often used — MONMSG MSGID(CPF0000) on its own, with no EXEC — means "ignore every error and carry on". Sometimes that is what you want, for instance after a DLTF on a file that may not exist. Far more often it is a way of hiding failures: the program carries on over data that is not there, and the error surfaces three steps later, where it no longer makes sense.

A MONMSG with no EXEC should only ever be written against the single command whose failure you have decided is acceptable, never across the whole program.

Variables

They start with &, have a declared type, and there is no implicit conversion between types.

DCL VAR(&COUNTER) TYPE(*DEC)  LEN(5 0) VALUE(0)
DCL VAR(&NAME)    TYPE(*CHAR) LEN(10)
DCL VAR(&FOUND)   TYPE(*LGL)  VALUE('0')

CHGVAR VAR(&COUNTER) VALUE(&COUNTER + 1)
CHGVAR VAR(&NAME) VALUE('CUSTOMER' *BCAT &CODE)

*CAT concatenates, *BCAT concatenates inserting a blank, *TCAT strips trailing blanks first. Three different operators, and getting them wrong produces names with spaces in the middle: an entire class of "object does not exist" errors on objects that do.

Reading system state

Commands starting with RTV return information into a variable instead of showing it on screen. They are what make a CL able to adapt:

RTVJOBA JOB(&JOBNAME) USER(&USER) NBR(&NUMBER) CURLIB(&CURLIB)
RTVSYSVAL SYSVAL(QDATE) RTNVAR(&DATE)
RTVOBJD OBJ(PRODLIB/ORDERS) OBJTYPE(*FILE) RTNLIB(&FOUNDLIB)

Tip

to find out whether an object exists there is CHKOBJ, and RTVOBJD works the same way: both send CPF9801 if the object is not there and CPF9802 if authority is missing. Monitor those two messages by name rather than CPF0000, so that "does not exist" and "no authority" stay two different cases.

CHKOBJ OBJ(PRODLIB/ORDINI) OBJTYPE(*FILE)
MONMSG MSGID(CPF9801) EXEC(GOTO CMDLBL(MISSING))

Reading a file

CL can read a file, but one record at a time and read-only:

DCLF FILE(PRODLIB/SETTINGS)

READ:
  RCVF
  MONMSG MSGID(CPF0864) EXEC(GOTO CMDLBL(DONE))
  ...
  GOTO CMDLBL(READ)
DONE:

CPF0864 is end of file, and it must always be monitored: it is how the loop ends.

This is not a language for processing data, and using it for that is a choice paid for in performance and readability. For reading a configuration parameter it is perfectly fine; for walking through a file, it is not.

OVRDBF, the powerful unknown

File overrides are among the most used and least explained tools:

OVRDBF FILE(ORDERS) TOFILE(TEST/ORDERS)
OVRDBF FILE(ORDERS) MBR(JANUARY)
OVRDBF FILE(REPORT) SHARE(*YES)

A program compiled to read ORDERS reads whatever the override puts in front of it: a file in another library, another member, with different open parameters. It is how one program serves twelve months of archive, or runs against test without being recompiled.

Warning

an override has a scope, the OVRSCOPE parameter. The default, *ACTGRPDFN, depends on the activation group of the program issuing it: in the default group it covers its call level and the ones below; in a named group it covers the whole group, which stays in the job after the return (see the ILE chapter). With OVRSCOPE(*JOB) it covers the whole job. In the last two cases a forgotten OVRDBF makes whatever comes next read the wrong file: overrides are always deleted, including on the error path, which is why DLTOVR appears twice in the example at the top of this chapter.

How it is compiled

CRTBNDCL PGM(PRODLIB/CLOSE) SRCFILE(PRODLIB/QCLSRC) SRCMBR(CLOSE)

CRTBNDCL produces an ILE program, and that is the form to use today; like CRTBNDRPG, by default it creates it with DFTACTGRP(*YES), in the default activation group. CRTCLPGM produces an old-model program: you meet it in historic code and there is no reason to use it for new work.

Sources

Back to the guide index

← Back to blog

Comments

No comments yet. Be the first to comment!

You need an account to comment. Log in · Sign up