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.
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.
Comments
No comments yet. Be the first to comment!
You need an account to comment. Log in · Sign up