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

CLControl 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.

It is taught little and written a lot, which is why so much of it is written badly.

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, before any other command, it applies to all of them: the general safety net.

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

RTVOBJD is also the clean way to find out whether an object exists: run it and monitor for CPF9801. It reads better than the shortcut you often see — a CHKOBJ followed by a silent MONMSG — and above all it says which error occurred, rather than treating "does not exist" and "you have no authority" the same way.

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, by default the call level where it was issued. An OVRDBF in a CL that then calls a program works; the same OVRDBF forgotten without a DLTOVR, in an interactive job that carries on working, stays active and makes everything afterwards 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. CRTCLPGM produces an old-model program: you meet it in historic code and there is no reason to use it for new work.

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