Conditions, IF and EVALUATE
|
This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production. The reference material consulted while preparing these pages is listed in the section bibliography. |
This page is written against ISO/IEC 1989:2023 (edition 3, January 2023; an edition 4 is upcoming, in DIS ballot), GnuCOBOL 3.2
(28 July 2023, the latest release) and IBM Enterprise COBOL for z/OS 6.5 (PTFs through September 2026), and was checked on
2026-10-20. Every portable example below was compiled with cobc -x -Wall (GnuCOBOL 3.2.0, fixed reference format) without a
single warning and run; the output and the diagnostics shown are real, and the few examples that need another option or that
are deliberately wrong say so. The ISO standard is paywalled and was not read: the rules come from the Enterprise COBOL
Language Reference and Programming Guide and from the GnuCOBOL Programmer’s Guide, and where the two differ the page says so.
A COBOL program makes decisions with a conditional expression: an expression whose value is true or false. The Language
Reference lists the statements that take one: IF, EVALUATE, PERFORM (the UNTIL phrase) and SEARCH (the WHEN
phrase). This page covers how conditions are written and combined, then the two statements that branch on them, IF and
EVALUATE. The data items the examples test come from the Circulation library back office: the 88-level names
BOOK-ACTIVE, BOOK-WITHDRAWN, MEMBER-ACTIVE, MEMBER-SUSPENDED, LOAN-CHECKOUT and LOAN-RETURN are defined in the copybooks
BOOKREC, MEMBREC and LOANREC, and how a level-88 entry is declared and set is in
VALUE, INITIALIZE and Condition Names. Loops that
end on a condition are in PERFORM and Structured Programming,
and the table search that uses the same conditions is in
Searching and Sorting Tables.
The six simple conditions
A condition is simple or complex. Enterprise COBOL 6.5 defines six simple conditions. Any number of paired parentheses may enclose a condition without changing whether it is simple or complex.
| Simple condition | What it tests | Example |
|---|---|---|
Relation |
Compares two operands: data items, literals, functions, arithmetic expressions, index-names, pointers |
|
Class |
Whether a data item holds only digits, only letters, only lowercase or uppercase letters, or only the characters of a user-defined class |
|
Sign |
Whether a numeric operand is positive, negative or zero |
|
Condition-name |
Whether a conditional variable has one of the values of a level-88 entry |
|
Switch-status |
Whether an external switch is on or off |
|
Omitted-argument |
Whether the caller left out an optional argument |
|
Each has a truth value, and the logical operators NOT, AND and OR combine them into complex conditions. The example
below uses every kind once, in the form that is accepted by both compilers (the UPSI-0 switch name needs
-std=ibm; see the section on switch-status conditions).
Example: every kind of simple condition
IDENTIFICATION DIVISION.
PROGRAM-ID. CONDKINDS.
ENVIRONMENT DIVISION.
CONFIGURATION SECTION.
SPECIAL-NAMES.
CLASS ISBN-CHAR IS "0" THRU "9" "X"
UPSI-0 IS DRY-RUN-SWITCH
ON STATUS IS DRY-RUN
OFF STATUS IS LIVE-RUN.
DATA DIVISION.
WORKING-STORAGE SECTION.
COPY BOOKREC.
COPY MEMBREC.
01 WS-CODE-SHORT PIC X(3) VALUE "ABC".
01 WS-CODE-LONG PIC X(5) VALUE "ABC ".
01 WS-ZIP-TEXT PIC X(5) VALUE "00042".
01 WS-ZIP-NUM PIC 9(5) VALUE 42.
01 WS-AMOUNT-1 PIC S9(5)V99 VALUE 12.5.
01 WS-AMOUNT-2 PIC 9(3)V9 VALUE 12.5.
01 WS-ADJUST PIC S9(3) VALUE -7.
01 WS-FLAG PIC X VALUE "N".
PROCEDURE DIVISION.
MAIN-PARA.
MOVE "9780000000011" TO BOOK-ISBN
MOVE "A" TO BOOK-STATUS
MOVE "S" TO MEMBER-STATUS
MOVE 12.50 TO MEMBER-FINES
* relation conditions
IF WS-CODE-SHORT = WS-CODE-LONG
DISPLAY "SHORT PADDED WITH SPACES: EQUAL"
END-IF
IF WS-ZIP-TEXT = WS-ZIP-NUM
DISPLAY "ALPHANUMERIC 00042 = NUMERIC 42: EQUAL"
END-IF
IF WS-AMOUNT-1 = WS-AMOUNT-2
DISPLAY "12.50 = 012.5: ALGEBRAIC COMPARISON"
END-IF
IF WS-CODE-SHORT GREATER THAN OR EQUAL TO "ABB"
DISPLAY "ABC >= ABB"
END-IF
IF WS-CODE-SHORT IS NOT LESS THAN "ABD"
DISPLAY "NOT REACHED"
ELSE
DISPLAY "ABC IS NOT LESS THAN ABD IS FALSE"
END-IF
* class conditions
IF BOOK-ISBN IS NUMERIC
DISPLAY "ISBN IS NUMERIC"
END-IF
IF BOOK-ISBN IS ISBN-CHAR
DISPLAY "ISBN USES ONLY ISBN-CHAR CHARACTERS"
END-IF
MOVE "97800000X0011" TO BOOK-ISBN
IF BOOK-ISBN IS NOT NUMERIC
DISPLAY "ISBN WITH X IS NOT NUMERIC"
END-IF
MOVE "COBOL BASICS" TO BOOK-TITLE
IF BOOK-TITLE IS ALPHABETIC
DISPLAY "COBOL BASICS IS ALPHABETIC"
END-IF
IF BOOK-TITLE IS ALPHABETIC-UPPER
DISPLAY "COBOL BASICS IS ALPHABETIC-UPPER"
END-IF
* sign conditions
IF MEMBER-FINES IS POSITIVE
DISPLAY "FINES POSITIVE"
END-IF
IF WS-ADJUST IS NEGATIVE
DISPLAY "ADJUSTMENT NEGATIVE"
END-IF
IF WS-ADJUST + 7 IS ZERO
DISPLAY "ADJUSTMENT + 7 IS ZERO"
END-IF
* condition-name conditions
IF MEMBER-SUSPENDED
DISPLAY "MEMBER SUSPENDED"
END-IF
IF NOT BOOK-WITHDRAWN
DISPLAY "BOOK NOT WITHDRAWN"
END-IF
* switch-status conditions
IF DRY-RUN
DISPLAY "DRY RUN"
END-IF
IF LIVE-RUN
DISPLAY "LIVE RUN"
END-IF
* combined conditions
IF MEMBER-SUSPENDED OR MEMBER-FINES > 10
AND NOT BOOK-WITHDRAWN
DISPLAY "COMBINED CONDITION TRUE"
END-IF
STOP RUN.
The program was compiled and run with cobc -x -Wall -std=ibm kinds.cob:
SHORT PADDED WITH SPACES: EQUAL
ALPHANUMERIC 00042 = NUMERIC 42: EQUAL
12.50 = 012.5: ALGEBRAIC COMPARISON
ABC >= ABB
ABC IS NOT LESS THAN ABD IS FALSE
ISBN IS NUMERIC
ISBN USES ONLY ISBN-CHAR CHARACTERS
ISBN WITH X IS NOT NUMERIC
COBOL BASICS IS ALPHABETIC
COBOL BASICS IS ALPHABETIC-UPPER
FINES POSITIVE
ADJUSTMENT NEGATIVE
ADJUSTMENT + 7 IS ZERO
MEMBER SUSPENDED
BOOK NOT WITHDRAWN
LIVE RUN
COMBINED CONDITION TRUE
The first five lines show the comparison rules described next; the class, sign, condition-name and switch-status lines show
the other kinds; the last line is a complex condition. UPSI-0 was off, so LIVE-RUN was true.
Relation conditions
A relation condition compares two operands with a relational operator. The word forms and the symbols are interchangeable;
NOT in front of the operator reverses it. The two characters of >= and <= must not be separated by a space, and every operator
must be preceded and followed by a space.
| Words | Symbol | Meaning |
|---|---|---|
|
|
greater than |
|
|
less than |
|
|
equal to |
|
|
greater than or equal to |
|
|
less than or equal to |
|
|
the opposite of each |
The comparison that is performed depends on the classes of the two operands (Enterprise COBOL Language Reference, Tables 23 and 24; the operand pairs that are not allowed are the blank cells in those tables). The rules that matter every day:
-
Numeric against numeric compares algebraic values. The number of digits, the
USAGE, the position of the decimal point and the sign representation are not significant; unsigned operands are positive, and all zero values are equal.12.50in aPIC S9(5)V99item equals12.5in aPIC 9(3)V9item, as in the example above. -
Alphanumeric against alphanumeric compares character by character from the left, using the collating sequence in use (the native one, or the one selected with
PROGRAM COLLATING SEQUENCE). If the operands differ in length, the shorter is treated as if it were extended to the right with spaces, so"ABC"equals"ABC ". -
A numeric integer against an alphanumeric item is allowed only for integers: the integer is treated as if it had been moved to an alphanumeric item with as many characters as the integer has digits.
00042in aPIC X(5)item equals thePIC 9(5)value 42. A numeric item with decimals against an alphanumeric item is not allowed by Enterprise COBOL (the table marks only integer items as permitted). GnuCOBOL 3.2.0 acceptsIF BOOK-TITLE = 12.5in every dialect without a diagnostic and the test is simply false, so the compiler will not catch it for you. -
Group items are compared as alphanumeric strings of bytes, without any conversion. Comparing a group that contains a packed or binary field with a numeric literal is therefore a comparison of bytes, which gives a result that is predictable only when both sides have the same data representation.
-
Figurative constants take the class of the other operand:
ZEROagainst a numeric item is a numeric comparison, against an alphanumeric item it is the character0;SPACEandHIGH-VALUEare characters (see Words, Literals and Figurative Constants). -
National and UTF-8 operands are compared in their own encodings, and a
PROGRAM COLLATING SEQUENCEhas no effect on UTF-8 comparisons. National Data, UTF-8 and Code Pages covers them. -
Pointers, index-names and object references have their own relation conditions (only equal and not equal for pointers; an index-name is compared by its occurrence number). See Pointers and Dynamic Storage and Tables and OCCURS.
A relation condition must contain at least one reference to an identifier, which is why IF "A" = "B" is not a meaningful
test; the next sections show what GnuCOBOL says about it.
Class conditions
A class condition tests the content of a data item. The Language Reference gives the forms that are valid for each kind of item (Table 21):
| Item referenced | Valid tests (each also with NOT) |
|---|---|
Alphabetic ( |
|
Alphanumeric, alphanumeric-edited, numeric-edited |
|
Zoned decimal ( |
|
National |
|
DBCS |
|
What each test means:
-
NUMERIC: only the digits 0 to 9, with or without an operational sign. If thePICTUREhas no sign, a sign in the data makes the item not numeric. If it has a sign, the item must be elementary and carry a valid sign. Which sign codes are valid depends on theNUMPROCcompiler option and theNUMCLSinstallation option, and invalid zone codes are treated as nonnumeric by the test even whenINVDATAtolerates them in comparisons. A group item can takeNUMERIConly if it has no signed elementary item. -
ALPHABETICis the letters A to Z, a to z and the space;ALPHABETIC-LOWERandALPHABETIC-UPPERare the lowercase and uppercase letters and the space. An all-spaces item therefore passes all three, and a numeric-lookingPIC Xitem passes none of them. -
A class-name is a user-defined class declared in
SPECIAL-NAMES: the item passes if it contains only the listed characters. -
DBCSandKANJIapply toDISPLAY-1items and range-check the byte pairs.
The example above declares CLASS ISBN-CHAR IS "0" THRU "9" "X", the character set of an ISBN-10 or ISBN-13 (the X
check digit), and tests BOOK-ISBN IS ISBN-CHAR. A value with an X in the middle still passes ISBN-CHAR but fails NUMERIC, as
the output shows.
GnuCOBOL is more permissive than the table above: its Programmer’s Guide says it has no restriction on group items or PIC A
items with NUMERIC, nor on PIC 9 items with ALPHABETIC, and 3.2.0 compiled an ALPHABETIC test on PIC 9 and COMP-3 items without any diagnostic. Code that
is meant to run on z/OS should stay inside Table 21.
Validating input with class tests
The numeric class test is the standard guard against invalid data. The compiler assumes that the contents of a data item agree
with its PICTURE and USAGE; when they do not (a record from another system, a field overwritten through a REDEFINES),
references to the item are undefined. Test before using. The program checks five raw transaction records before it trusts
LOANREC:
IDENTIFICATION DIVISION.
PROGRAM-ID. LOANVALID.
DATA DIVISION.
WORKING-STORAGE SECTION.
COPY LOANREC.
01 WS-RAW-VALUES.
05 FILLER PIC X(21) VALUE "O00010000000120261020".
05 FILLER PIC X(21) VALUE "O00A10000000120261020".
05 FILLER PIC X(21) VALUE "R 00000120261020".
05 FILLER PIC X(21) VALUE "X00010000000120261020".
05 FILLER PIC X(21) VALUE "O0001000000012026102 ".
01 WS-RAW-TABLE REDEFINES WS-RAW-VALUES.
05 WS-RAW PIC X(21) OCCURS 5 TIMES.
01 WS-SUB PIC 9 VALUE ZERO.
01 WS-MEMBER-TEXT PIC X(6) VALUE SPACES.
PROCEDURE DIVISION.
MAIN-PARA.
PERFORM VARYING WS-SUB FROM 1 BY 1 UNTIL WS-SUB > 5
MOVE WS-RAW (WS-SUB) TO LOAN-RECORD
IF (LOAN-CHECKOUT OR LOAN-RETURN)
AND LOAN-MEMBER-ID IS NUMERIC
AND LOAN-BOOK-ID IS NUMERIC
AND LOAN-DATE IS NUMERIC
DISPLAY WS-SUB ": VALID " WS-RAW (WS-SUB)
ELSE
DISPLAY WS-SUB ": REJECT " WS-RAW (WS-SUB)
END-IF
END-PERFORM
IF WS-MEMBER-TEXT IS NUMERIC
DISPLAY "SPACES ARE NUMERIC"
ELSE
DISPLAY "SPACES ARE NOT NUMERIC"
END-IF
IF WS-MEMBER-TEXT IS ALPHABETIC
DISPLAY "SPACES ARE ALPHABETIC"
END-IF
STOP RUN.
1: VALID O00010000000120261020
2: REJECT O00A10000000120261020
3: REJECT R 00000120261020
4: REJECT X00010000000120261020
5: REJECT O0001000000012026102
SPACES ARE NOT NUMERIC
SPACES ARE ALPHABETIC
Source: Enterprise COBOL for z/OS 6.5 Programming Guide: Checking for incompatible data (numeric class test)
The parentheses around LOAN-CHECKOUT OR LOAN-RETURN matter, and the next sections explain why. Record 3 shows that a blank
field is not numeric (but is alphabetic, as the last two lines show for a PIC X item). On z/OS the NUMCHECK(ZON,PAC) compiler
option can have the compiler generate implicit numeric class tests for zoned and packed decimal items that are sending
items, which validates sign fields against the NUMPROC option too. Where the data comes from and how a MOVE treats it is in
MOVE and Data Movement.
Sign conditions
A sign condition tests whether a numeric operand is POSITIVE (greater than zero), NEGATIVE (less than zero) or ZERO; NOT
reverses it, and NOT ZERO is true for a positive or a negative value. The operand can be a numeric identifier or an arithmetic
expression that contains at least one variable, so IF WS-ADJUST + 7 IS ZERO is valid, as in the example. An unsigned operand is either
positive or zero. On z/OS the result depends on the NUMPROC option.
Condition-name conditions
A condition-name is the name of a level-88 entry. The condition MEMBER-SUSPENDED is true when the conditional variable
MEMBER-STATUS has any of the values (or lies in any of the THRU ranges) of the entry. The rules of comparison are those of
relation conditions, and it is the same as writing the relation by hand: IF MEMBER-SUSPENDED replaces
IF MEMBER-STATUS = "S". Condition-names are allowed for alphanumeric, DBCS, national, UTF-8 and floating-point items as well as
numeric ones. Prefer the name to the literal: the meaning of the value is written once, in the data division, and
SET MEMBER-SUSPENDED TO TRUE changes the state without repeating the code. That SET statement, and the WHEN SET TO FALSE
phrase, are in the page on
VALUE, INITIALIZE and Condition Names.
Switch-status conditions
A switch-status condition tests an external on/off switch. The programmer associates a condition-name with the on or off value of
a switch in SPECIAL-NAMES, then tests the name:
-
In Enterprise COBOL the switch is one of the eight UPSI (user-programmable status indicator) switches
UPSI-0toUPSI-7; the name is associated with the value 1 (on) or 0 (off). The value is set outside the program, for example to select a year-end run. -
In GnuCOBOL the switch is
SWITCH-0toSWITCH-36, and it is set from the environment withCOB_SWITCH_n=ONbefore the run (COB_SWITCH_1forSWITCH-1). -
GnuCOBOL 3.2.0 accepts
UPSI-0as a synonym ofSWITCH-0only with-std=ibm. Compiled without it, the example gives the error below, and with-std=ibmthe nameSWITCH-1is rejected in the same way. SettingCOB_SWITCH_0=ONfor the-std=ibmbuild of the example madeDRY-RUNtrue, andCOB_SWITCH_1=ONleft it false.
kinds.cob:7: error: invalid system-name 'UPSI-0'
5 | SPECIAL-NAMES.
6 | CLASS ISBN-CHAR IS "0" THRU "9" "X"
7 > UPSI-0 IS DRY-RUN-SWITCH
This output is from compiling the example above without -std=ibm. Switch conditions are a way to vary a run without
recompiling, such as a dry-run mode for the nightly run; for anything more than a few modes, read the setting from a
parameter or a file instead.
The omitted-argument condition
A called program can make a parameter optional and test whether the caller passed it. Enterprise COBOL 6.5 documents the
OPTIONAL phrase of PROCEDURE DIVISION USING (it is not allowed when the LP(64) compiler option is in effect) and the
condition data-name IS [NOT] OMITTED. The condition is true when the caller passed OMITTED for that argument, when the
argument was a trailing one that the call left out, or when the argument is itself an omitted formal parameter of the caller.
The data item must be defined in the program that tests it and be named in an OPTIONAL phrase. The GnuCOBOL 3.2
Programmer’s Guide lists OMITTED among its class conditions. The example is a program pair that calls LOANDAYS once with
a loan period and once without:
IDENTIFICATION DIVISION.
PROGRAM-ID. OMITMAIN.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 WS-LIMIT PIC 9(3) VALUE 21.
PROCEDURE DIVISION.
MAIN-PARA.
CALL "LOANDAYS" USING WS-LIMIT
CALL "LOANDAYS" USING OMITTED
STOP RUN.
END PROGRAM OMITMAIN.
IDENTIFICATION DIVISION.
PROGRAM-ID. LOANDAYS.
DATA DIVISION.
LINKAGE SECTION.
01 LK-DAYS PIC 9(3).
PROCEDURE DIVISION USING OPTIONAL LK-DAYS.
MAIN-PARA.
IF LK-DAYS IS OMITTED
DISPLAY "NO LIMIT PASSED: DEFAULT 14 DAYS"
ELSE
DISPLAY "LIMIT PASSED: " LK-DAYS " DAYS"
END-IF
GOBACK.
END PROGRAM LOANDAYS.
Compiled with cobc -x -Wall omit.cob:
LIMIT PASSED: 021 DAYS
NO LIMIT PASSED: DEFAULT 14 DAYS
With -std=ibm the same file compiles, runs identically, and gives warning: USING OPTIONAL used [-Wdialect], so a build that
must be free of warnings in that dialect cannot use it. The way arguments are passed, BY REFERENCE, BY CONTENT and
BY VALUE, and what OMITTED means in the CALL statement, are in
Subprograms, CALL and Linkage.
Complex conditions: NOT, AND and OR
Simple conditions are combined with the logical operators, each of which must be preceded and followed by a space.
| Operator | Name | True when |
|---|---|---|
|
logical negation |
the condition after it is false |
|
logical conjunction |
both conditions are true |
|
logical inclusive or |
either or both conditions are true |
Precedence from highest to lowest: arithmetic operations, the simple conditions themselves, NOT, AND, OR. Parentheses
override it. The Language Reference gives this example, which is evaluated as the parenthesized form below it:
NOT A IS GREATER THAN B OR A + B IS EQUAL TO C AND D IS POSITIVE
(NOT (A IS GREATER THAN B)) OR
(((A + B) IS EQUAL TO C) AND (D IS POSITIVE))
Order of evaluation. Conditions within one level are evaluated from left to right, and evaluation of that level stops as soon as
its truth value is known; arithmetic expressions and functions inside a condition are evaluated only if and when that condition
is reached. This is what makes a guard such as "is the subscript valid, and only then the table entry" safe. GnuCOBOL 3.2.0
behaves the same way. In the program below the first test is guarded and the second is not; with -debug the second one is
caught by the subscript check:
IDENTIFICATION DIVISION.
PROGRAM-ID. SHORTCIRC.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 WS-I PIC 9 VALUE ZERO.
01 WS-TABLE.
05 WS-ENTRY PIC X OCCURS 3 TIMES VALUE "A".
PROCEDURE DIVISION.
MAIN-PARA.
IF WS-I > 0 AND WS-ENTRY (WS-I) = "A"
DISPLAY "FOUND"
ELSE
DISPLAY "GUARDED"
END-IF
IF WS-ENTRY (WS-I) = "A" AND WS-I > 0
DISPLAY "FOUND"
ELSE
DISPLAY "NOT GUARDED"
END-IF
STOP RUN.
Compiled with cobc -x -Wall -debug shortcirc.cob; the first line is the output of the guarded test and the rest is the
runtime error of the second one (the program stops there and returns exit status 1):
GUARDED
libcob: shortcirc.cob:15: error: subscript of 'WS-ENTRY' out of bounds: 0
Last statement of "SHORTCIRC" was IF
MAIN-PARA at shortcirc.cob:15
ENTRY SHORTCIRC at shortcirc.cob:9
Started by ./shortcirc.out
Without -debug GnuCOBOL does not check the subscript: the run without it printed NOT GUARDED, having read the storage in front of the table.
Table subscript checking options are covered in
Tables and OCCURS.
Two rules help a reader: write the cheap or protective test first, and put parentheses around any mix of AND and OR
even where the precedence rules would give the same answer. Parentheses are never needed when a condition uses only AND
or only OR. The Language Reference also notes that NOT A IS EQUAL TO B and NOT (A IS EQUAL TO B) are the same.
Abbreviated combined relation conditions
When relation conditions are written one after another, any condition after the first can omit its subject, or its subject and its relational operator. The omitted parts are taken from the last stated subject and the last stated operator:
| Abbreviated | Unabbreviated equivalent |
|---|---|
|
|
|
|
|
|
|
|
(The first four rows of Table 31 in the Language Reference.) The rules, in the order in which they cause trouble:
-
NOTis part of the relational operator when it is immediately followed byGREATER THAN,>,LESS THAN,<,EQUAL TOor=. In any other position it is a logicalNOTthat negates only the single relation condition that follows it: the negation is not carried to the objects that are abbreviated after it. -
A parenthesis right after a relational operator distributes that operator over the objects inside it, as in
WS-COPIES = (3 OR 4 OR 5). A distributed operator may not contain a simple condition, another relational operator, or aNOTimmediately after the left parenthesis. -
The insertion of an omitted subject or operator stops when another simple condition (including a condition-name) is found, or at the right parenthesis that closes a parenthesis opened to the left of the subject.
-
Two consecutive logical
NOT`s cancel each other and produce an S-level message; `A = B AND NOT NOT = Cis legal, because the secondNOTbelongs to the operator.
Example: the abbreviated forms, and the classic trap
The program prints the truth value of five conditions for a number of copies from 0 to 11. Column A is > 2 AND < 10 with
the subject omitted. Columns B and C are the trap: NOT = 1 OR 2 means "not 1, or not 2" and is true for every number,
while the condition that was probably meant is NOT = 1 AND 2. Column D omits the operator as well, and column E distributes
= over a parenthesized list. The last four lines show NOT and the precedence of AND:
IDENTIFICATION DIVISION.
PROGRAM-ID. ABBREV.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 WS-COPIES PIC 9(3).
01 WS-RESULTS.
05 WS-R1 PIC X(5).
05 WS-R2 PIC X(5).
05 WS-R3 PIC X(5).
05 WS-R4 PIC X(5).
05 WS-R5 PIC X(5).
01 WS-MEMBER-STATUS PIC X.
01 WS-A PIC 9 VALUE 1.
01 WS-B PIC 9 VALUE 0.
01 WS-C PIC 9 VALUE 0.
PROCEDURE DIVISION.
MAIN-PARA.
DISPLAY "COPIES A B C D E"
PERFORM VARYING WS-COPIES FROM 0 BY 1 UNTIL WS-COPIES > 11
* A: abbreviated subject, same as (COPIES > 2) AND (COPIES < 10)
IF WS-COPIES > 2 AND < 10
MOVE "TRUE " TO WS-R1
ELSE
MOVE "FALSE" TO WS-R1
END-IF
* B: the classic trap, (COPIES NOT = 1) OR (COPIES NOT = 2)
IF WS-COPIES NOT = 1 OR 2
MOVE "TRUE " TO WS-R2
ELSE
MOVE "FALSE" TO WS-R2
END-IF
* C: what B was meant to say
IF WS-COPIES NOT = 1 AND 2
MOVE "TRUE " TO WS-R3
ELSE
MOVE "FALSE" TO WS-R3
END-IF
* D: operator abbreviated too, (COPIES < 3) OR (COPIES > 9)
IF WS-COPIES < 3 OR > 9
MOVE "TRUE " TO WS-R4
ELSE
MOVE "FALSE" TO WS-R4
END-IF
* E: parentheses distribute the operator over the objects
IF WS-COPIES = (3 OR 4 OR 5)
MOVE "TRUE " TO WS-R5
ELSE
MOVE "FALSE" TO WS-R5
END-IF
DISPLAY WS-COPIES " " WS-R1 " " WS-R2 " " WS-R3
" " WS-R4 " " WS-R5
END-PERFORM
MOVE "B" TO WS-MEMBER-STATUS
* NOT before an unrelated relation negates only that relation:
* (NOT (STATUS = "A")) OR (STATUS = "B")
IF NOT WS-MEMBER-STATUS = "A" OR "B"
DISPLAY "NOT STATUS = A OR B: TRUE FOR B"
ELSE
DISPLAY "NOT STATUS = A OR B: FALSE FOR B"
END-IF
* parentheses negate the whole alternative
IF NOT (WS-MEMBER-STATUS = "A" OR "B")
DISPLAY "NOT (STATUS = A OR B): TRUE FOR B"
ELSE
DISPLAY "NOT (STATUS = A OR B): FALSE FOR B"
END-IF
* AND binds tighter than OR: A = 1 OR (B = 1 AND C = 1)
IF WS-A = 1 OR WS-B = 1 AND WS-C = 1
DISPLAY "A OR B AND C: TRUE"
ELSE
DISPLAY "A OR B AND C: FALSE"
END-IF
IF (WS-A = 1 OR WS-B = 1) AND WS-C = 1
DISPLAY "(A OR B) AND C: TRUE"
ELSE
DISPLAY "(A OR B) AND C: FALSE"
END-IF
STOP RUN.
COPIES A B C D E
000 FALSE TRUE TRUE TRUE FALSE
001 FALSE TRUE FALSE TRUE FALSE
002 FALSE TRUE FALSE TRUE FALSE
003 TRUE TRUE TRUE FALSE TRUE
004 TRUE TRUE TRUE FALSE TRUE
005 TRUE TRUE TRUE FALSE TRUE
006 TRUE TRUE TRUE FALSE FALSE
007 TRUE TRUE TRUE FALSE FALSE
008 TRUE TRUE TRUE FALSE FALSE
009 TRUE TRUE TRUE FALSE FALSE
010 FALSE TRUE TRUE TRUE FALSE
011 FALSE TRUE TRUE TRUE FALSE
NOT STATUS = A OR B: TRUE FOR B
NOT (STATUS = A OR B): FALSE FOR B
A OR B AND C: TRUE
(A OR B) AND C: FALSE
Column B is true on every row: that is the consequence of the rule, and the compiler does not warn about it (the program
compiles clean with -Wall, and also with -std=ibm). The same program shows that NOT STATUS = "A" OR "B" is true for a
status of B, whereas NOT (STATUS = "A" OR "B") is false for it. The GnuCOBOL Programmer’s Guide, in its section on combined conditions, shows only the plain
abbreviation ACCOUNT-STATUS = 1 OR 2 OR 7; the behaviour of 3.2.0 on the forms tried above agrees with the IBM description.
Abbreviated conditions are compact, and the compact form is the one people misread. Use them for the plain forms (a list of
values, a range written X > 2 AND < 10), and for anything involving NOT spell the conditions out or use a level-88 name
that lists the values or a THRU range.
The IF statement
IF evaluates a condition and runs one of two alternative statements. In the form to use today:
IF condition [THEN]
statement-1 ...
[ELSE
statement-2 ...]
END-IF
-
THENis optional and has no effect on the program. -
statement-1runs when the condition is true andstatement-2when it is false. Each is a series of imperative statements, and may include another conditional statement, in which case theIFis nested. -
END-IFis the explicit scope terminator. Without it, the scope of theIFends at the next separator period (and anELSEbelongs to the nearest precedingIFthat has not been terminated). -
NEXT SENTENCE, orCONTINUE, can stand where a statement is required. See the section below.
Nested IF statements are matched left to right: an ELSE goes with the nearest preceding IF that has no ELSE and has not been
terminated, and an END-IF goes with the nearest preceding IF that has not been terminated. With END-IF on every
IF the matching is explicit and the indentation tells the truth. If the ELSE is omitted, every statement between the
condition and the END-IF (or the period) belongs to the true branch.
Source: Enterprise COBOL for z/OS 6.5 Language Reference: IF statement and GnuCOBOL Programmer’s Guide: IF
Explicit scope terminators and the period
Every conditional statement (IF, EVALUATE, READ, SEARCH, an ADD with an ON SIZE ERROR phrase, and so on) has an explicit
scope terminator (END-IF, END-EVALUATE, END-READ, END-SEARCH, END-ADD) that turns it into an imperative statement. A sentence is one or more
statements ended by a period. The period is the implicit scope terminator: it ends every statement that is still open, however deeply nested,
and that is the cause of the best-known COBOL bug.
The stray period
A period inside a block of statements ends the whole sentence, including the IF that contains it. Everything after it runs
unconditionally. The next program counts three members, one of them suspended. BUGGY-CHECK is deliberately wrong: the
author wanted to count a suspended member as rejected and post the others, and put a period after the ADD that counts the rejection. The
ADD 1 TO WS-POSTED that follows then belongs to no IF at all. FIXED-CHECK states the intent with ELSE and END-IF:
IDENTIFICATION DIVISION.
PROGRAM-ID. STRAYDOT.
DATA DIVISION.
WORKING-STORAGE SECTION.
COPY MEMBREC.
01 WS-STATUS-VALUES PIC X(3) VALUE "ASA".
01 WS-STATUS-TABLE REDEFINES WS-STATUS-VALUES.
05 WS-STATUS PIC X OCCURS 3 TIMES.
01 WS-SUB PIC 9 VALUE ZERO.
01 WS-POSTED PIC 9 VALUE ZERO.
01 WS-REJECTED PIC 9 VALUE ZERO.
PROCEDURE DIVISION.
MAIN-PARA.
DISPLAY "STRAY PERIOD"
MOVE ZERO TO WS-POSTED WS-REJECTED
PERFORM VARYING WS-SUB FROM 1 BY 1 UNTIL WS-SUB > 3
MOVE WS-STATUS (WS-SUB) TO MEMBER-STATUS
PERFORM BUGGY-CHECK
END-PERFORM
DISPLAY " POSTED " WS-POSTED " REJECTED " WS-REJECTED
DISPLAY "SCOPE TERMINATOR"
MOVE ZERO TO WS-POSTED WS-REJECTED
PERFORM VARYING WS-SUB FROM 1 BY 1 UNTIL WS-SUB > 3
MOVE WS-STATUS (WS-SUB) TO MEMBER-STATUS
PERFORM FIXED-CHECK
END-PERFORM
DISPLAY " POSTED " WS-POSTED " REJECTED " WS-REJECTED
STOP RUN.
BUGGY-CHECK.
IF MEMBER-SUSPENDED
ADD 1 TO WS-REJECTED.
ADD 1 TO WS-POSTED.
FIXED-CHECK.
IF MEMBER-SUSPENDED
ADD 1 TO WS-REJECTED
ELSE
ADD 1 TO WS-POSTED
END-IF.
STRAY PERIOD
POSTED 3 REJECTED 1
SCOPE TERMINATOR
POSTED 2 REJECTED 1
All three members were posted by the buggy version, including the suspended one, and the compiler said nothing. The bug is silent
because a period after a statement is legal almost everywhere. The signs to look for: a period in the middle of a paragraph, a
period at the end of a line that continues an IF visually, and a statement that "always runs" even though it is indented
under an IF. The cure is a habit rather than a tool:
-
give every
IF,EVALUATE,READ,SEARCHandPERFORMits scope terminator, and write a single period, on a line of its own or at the end of the last statement, to end each paragraph; -
never put a period inside a paragraph other than at its end;
-
indent by the structure, not by the period.
When the period comes before an ELSE, the compiler can see the problem, because an ELSE with no open IF is a syntax error. This
deliberately wrong program puts a period after the first branch:
IDENTIFICATION DIVISION.
PROGRAM-ID. BADELSE.
DATA DIVISION.
WORKING-STORAGE SECTION.
COPY MEMBREC.
01 WS-REJECTED PIC 9 VALUE ZERO.
01 WS-POSTED PIC 9 VALUE ZERO.
PROCEDURE DIVISION.
MAIN-PARA.
IF MEMBER-SUSPENDED
ADD 1 TO WS-REJECTED.
ELSE
ADD 1 TO WS-POSTED
END-IF.
STOP RUN.
Real diagnostics from cobc -x -Wall badelse.cob:
badelse.cob: in paragraph 'MAIN-PARA':
badelse.cob:12: error: syntax error, unexpected ELSE
10 | IF MEMBER-SUSPENDED
11 | ADD 1 TO WS-REJECTED.
12 > ELSE
13 | ADD 1 TO WS-POSTED
14 | END-IF.
badelse.cob:14: error: syntax error, unexpected END-IF
12 | ELSE
13 | ADD 1 TO WS-POSTED
14 > END-IF.
15 | STOP RUN.
16 | <EOF>
The second error is a consequence of the first: the END-IF has no IF to close.
NEXT SENTENCE versus CONTINUE
CONTINUE is a no-operation statement: it says that no executable instruction is present, and control passes to the next
statement. NEXT SENTENCE is different, and it is an old construct you may meet in programs written before the 1985 standard: control
passes to an implicit CONTINUE immediately after the next separator period. In an IF that is terminated by a period the two behave
the same, as the program below shows (the NEXT SENTENCE paragraph is old code you may meet, kept here to read it with the modern form
next to it):
IDENTIFICATION DIVISION.
PROGRAM-ID. NEXTSENT.
DATA DIVISION.
WORKING-STORAGE SECTION.
COPY MEMBREC.
01 WS-STATUS-VALUES PIC X(3) VALUE "ASA".
01 WS-STATUS-TABLE REDEFINES WS-STATUS-VALUES.
05 WS-STATUS PIC X OCCURS 3 TIMES.
01 WS-SUB PIC 9 VALUE ZERO.
PROCEDURE DIVISION.
MAIN-PARA.
DISPLAY "NEXT SENTENCE"
PERFORM VARYING WS-SUB FROM 1 BY 1 UNTIL WS-SUB > 3
MOVE WS-STATUS (WS-SUB) TO MEMBER-STATUS
PERFORM NEXT-SENTENCE-CHECK
END-PERFORM
DISPLAY "CONTINUE"
PERFORM VARYING WS-SUB FROM 1 BY 1 UNTIL WS-SUB > 3
MOVE WS-STATUS (WS-SUB) TO MEMBER-STATUS
PERFORM CONTINUE-CHECK
END-PERFORM
STOP RUN.
NEXT-SENTENCE-CHECK.
IF MEMBER-SUSPENDED
NEXT SENTENCE
ELSE
DISPLAY " ACTIVE MEMBER " WS-SUB.
DISPLAY " END OF CHECK " WS-SUB.
CONTINUE-CHECK.
IF MEMBER-SUSPENDED
CONTINUE
ELSE
DISPLAY " ACTIVE MEMBER " WS-SUB
END-IF
DISPLAY " END OF CHECK " WS-SUB.
Compiled with cobc -x -Wall nextsent.cob. Real output of the compiler, a warning that stops a "no warnings" build, followed by the run:
nextsent.cob: in paragraph 'NEXT-SENTENCE-CHECK':
nextsent.cob:26: warning: NEXT SENTENCE is archaic in GnuCOBOL [-Warchaic]
24 | NEXT-SENTENCE-CHECK.
25 | IF MEMBER-SUSPENDED
26 > NEXT SENTENCE
27 | ELSE
28 | DISPLAY " ACTIVE MEMBER " WS-SUB.
NEXT SENTENCE
ACTIVE MEMBER 1
END OF CHECK 1
END OF CHECK 2
ACTIVE MEMBER 3
END OF CHECK 3
CONTINUE
ACTIVE MEMBER 1
END OF CHECK 1
END OF CHECK 2
ACTIVE MEMBER 3
END OF CHECK 3
The hazard is NEXT SENTENCE in an IF that also has END-IF. The Language Reference says that then control does not pass to
the statement after END-IF: it passes to the statement after the closest following period, which can be the end of the
paragraph, so the statements between END-IF and the period are skipped. The GnuCOBOL Programmer’s Guide says that NEXT SENTENCE
and END-IF cannot be used in the same IF statement. This is why this guide does not use NEXT SENTENCE in new code and
does not demonstrate the combination: write CONTINUE for "do nothing here" and restructure with NOT if the empty
branch bothers you. GnuCOBOL 3.2.0 warns about NEXT SENTENCE (-Warchaic) under -Wall. The remaining
older constructs of this kind are collected in
Legacy Features and Migration.
The EVALUATE statement
EVALUATE is COBOL’s multi-way decision: a shorthand for a series of nested `IF`s that can test several things at once. It is
the right tool for decision tables.
EVALUATE subject [ALSO subject] ...
WHEN object [ALSO object] ...
imperative-statement ...
[WHEN object [ALSO object] ...
imperative-statement ...] ...
[WHEN OTHER
imperative-statement ...]
END-EVALUATE
The operands between EVALUATE and the first WHEN are the selection subjects, separated by ALSO. Each WHEN has one selection
object for every subject, separated by ALSO, and the objects are matched with the subjects by position. The rules:
-
A subject is an identifier, a literal, an arithmetic expression, a conditional expression (a condition), or the words
TRUEorFALSE. -
An object is an identifier, a literal or an arithmetic expression (compared for equality with the subject), a range
value-1 THRU value-2(both ends of the same class; the subject matches if it is within the range, ends included; a range whose first operand is greater than the second matches nothing), a condition,TRUEorFALSE, orANY, which matches whatever the subject is.NOTin front of a value or a range matches everything but it. -
Subject and object kinds must correspond: a condition or
TRUE/FALSEas an object needs a conditional expression (orTRUE/FALSE) as the subject in the same position. -
The WHEN phrases are tried in the order written and the first one whose objects are all satisfied is selected. There is no fall-through to the next
WHENafter its statements, so nobreakis needed, and order is the way to give one rule priority over another. -
Several WHEN phrases can share one set of statements: a
WHENwith no statement of its own uses the statements of the nextWHENthat has some (the"O" ALSO "A"and"o" ALSO "A"phrases below). -
WHEN OTHER runs when no
WHENmatched. If it is missing and nothing matched, execution continues afterEND-EVALUATEand nothing happens, which is rarely what you want for a business rule; codeWHEN OTHERand make it report. -
The number of objects in each
WHENmust be the same as the number of subjects (the compiler checks it; see the diagnostics below).
Example: values, ranges, ALSO, TRUE and FALSE
IDENTIFICATION DIVISION.
PROGRAM-ID. EVALFORMS.
DATA DIVISION.
WORKING-STORAGE SECTION.
COPY MEMBREC.
COPY LOANREC.
01 WS-FINE-VALUES.
05 FILLER PIC 9(5)V99 VALUE 0.
05 FILLER PIC 9(5)V99 VALUE 0.01.
05 FILLER PIC 9(5)V99 VALUE 9.99.
05 FILLER PIC 9(5)V99 VALUE 10.
05 FILLER PIC 9(5)V99 VALUE 49.99.
05 FILLER PIC 9(5)V99 VALUE 50.
05 FILLER PIC 9(5)V99 VALUE 120.
01 WS-FINE-TABLE REDEFINES WS-FINE-VALUES.
05 WS-FINE PIC 9(5)V99 OCCURS 7 TIMES.
01 WS-CASE-VALUES PIC X(12) VALUE "OAOSRSRAoAXA".
01 WS-CASE-TABLE REDEFINES WS-CASE-VALUES.
05 WS-CASE OCCURS 6 TIMES.
10 WS-CASE-TYPE PIC X.
10 WS-CASE-STATUS PIC X.
01 WS-SUB PIC 9 VALUE ZERO.
01 WS-FINE-EDIT PIC ZZZZ9.99.
01 WS-TIER PIC X(8).
01 WS-ACTION PIC X(24).
PROCEDURE DIVISION.
MAIN-PARA.
PERFORM VARYING WS-SUB FROM 1 BY 1 UNTIL WS-SUB > 7
MOVE WS-FINE (WS-SUB) TO MEMBER-FINES WS-FINE-EDIT
EVALUATE MEMBER-FINES
WHEN ZERO
MOVE "NONE" TO WS-TIER
WHEN 0.01 THRU 9.99
MOVE "NOTICE" TO WS-TIER
WHEN 10 THRU 49.99
MOVE "HOLD" TO WS-TIER
WHEN OTHER
MOVE "SUSPEND" TO WS-TIER
END-EVALUATE
DISPLAY "FINES " WS-FINE-EDIT " -> " WS-TIER
END-PERFORM
PERFORM VARYING WS-SUB FROM 1 BY 1 UNTIL WS-SUB > 6
MOVE WS-CASE-TYPE (WS-SUB) TO LOAN-TYPE
MOVE WS-CASE-STATUS (WS-SUB) TO MEMBER-STATUS
EVALUATE LOAN-TYPE ALSO MEMBER-STATUS
WHEN "O" ALSO "A"
WHEN "o" ALSO "A"
MOVE "CHECK OUT" TO WS-ACTION
WHEN "O" ALSO "S"
MOVE "REJECT, MEMBER SUSPENDED" TO WS-ACTION
WHEN "R" ALSO ANY
MOVE "ACCEPT RETURN" TO WS-ACTION
WHEN OTHER
MOVE "UNKNOWN TRANSACTION" TO WS-ACTION
END-EVALUATE
DISPLAY LOAN-TYPE MEMBER-STATUS " -> " WS-ACTION
END-PERFORM
MOVE "R" TO LOAN-TYPE
EVALUATE TRUE
WHEN LOAN-CHECKOUT
DISPLAY "TRUE: CHECKOUT"
WHEN LOAN-RETURN
DISPLAY "TRUE: RETURN"
WHEN OTHER
DISPLAY "TRUE: OTHER"
END-EVALUATE
EVALUATE FALSE
WHEN LOAN-CHECKOUT
DISPLAY "FALSE: NOT A CHECKOUT"
WHEN OTHER
DISPLAY "FALSE: OTHER"
END-EVALUATE
STOP RUN.
Output:
FINES 0.00 -> NONE
FINES 0.01 -> NOTICE
FINES 9.99 -> NOTICE
FINES 10.00 -> HOLD
FINES 49.99 -> HOLD
FINES 50.00 -> SUSPEND
FINES 120.00 -> SUSPEND
OA -> CHECK OUT
OS -> REJECT, MEMBER SUSPENDED
RS -> ACCEPT RETURN
RA -> ACCEPT RETURN
oA -> CHECK OUT
XA -> UNKNOWN TRANSACTION
TRUE: RETURN
FALSE: NOT A CHECKOUT
Source: Enterprise COBOL for z/OS 6.5 Language Reference: EVALUATE statement and GnuCOBOL Programmer’s Guide: EVALUATE
-
The first loop is
EVALUATEon one subject,MEMBER-FINES, with aTHRUrange per fines tier andWHEN OTHERfor the rest. A range is a range of the comparison, so0.01 THRU 9.99includes every value in between with the decimals the item has, and a value above49.99and below50.00can only exist if the item has more decimals. Keep the tiers contiguous. -
The second loop has two subjects joined with
ALSO(the transaction type and the member status)."R" ALSO ANYaccepts a return for any member;"O"and the lowercase"o"share the checkout statements;XAfalls toWHEN OTHER. -
EVALUATE TRUEmatches the firstWHENwhose condition is true. It is the form to use when the tests are not all on the same field: eachWHENis a complete condition, and the order sets the priority.EVALUATE FALSEmatches the conditions that are false.
GnuCOBOL 3.2.0 also accepts a partial expression as a selection object, a relation with no left operand such as WHEN < 5,
and a class or sign condition without its subject; its Programmer’s Guide documents it. The Enterprise COBOL format has no such object. The
program below compiles without any diagnostic in the default dialect and with -std=ibm, and prints PLENTY; the portable form is
EVALUATE TRUE with WHEN BOOK-COPIES < 5.
IDENTIFICATION DIVISION.
PROGRAM-ID. PARTIAL.
DATA DIVISION.
WORKING-STORAGE SECTION.
COPY BOOKREC.
PROCEDURE DIVISION.
MAIN-PARA.
MOVE 7 TO BOOK-COPIES
EVALUATE BOOK-COPIES
WHEN ZERO
DISPLAY "NONE"
WHEN < 5
DISPLAY "FEW"
WHEN OTHER
DISPLAY "PLENTY"
END-EVALUATE
STOP RUN.
Example: a decision table for loan rules with EVALUATE TRUE ALSO
A decision table has one column for every thing you test and one row for every rule; EVALUATE TRUE ALSO TRUE ALSO TRUE is that table in code. The
three subjects are the transaction, the member and the book, and every object is a condition (or ANY for "don’t care"). The rules
of the nightly run are, in priority order: a return is always accepted; a checkout is rejected for a suspended member, for a withdrawn
book and for a book with no copies; it is held for a member whose fines exceed the limit; any other checkout is accepted.
The table has seven rows including WHEN OTHER, and the demonstration drives it with seven cases:
IDENTIFICATION DIVISION.
PROGRAM-ID. LOANRULE.
DATA DIVISION.
WORKING-STORAGE SECTION.
COPY BOOKREC.
COPY MEMBREC.
COPY LOANREC.
01 WS-FINE-LIMIT PIC 9(5)V99 VALUE 10.
01 WS-CASE-VALUES.
05 FILLER PIC X(13) VALUE "OA0000000A003".
05 FILLER PIC X(13) VALUE "OS0005000A001".
05 FILLER PIC X(13) VALUE "OA0001250A002".
05 FILLER PIC X(13) VALUE "OA0000000W001".
05 FILLER PIC X(13) VALUE "OA0000000A000".
05 FILLER PIC X(13) VALUE "RS0005000A001".
05 FILLER PIC X(13) VALUE "XA0000000A001".
01 WS-CASE-TABLE REDEFINES WS-CASE-VALUES.
05 WS-CASE OCCURS 7 TIMES.
10 WS-CASE-TYPE PIC X.
10 WS-CASE-MEMBER PIC X.
10 WS-CASE-FINES PIC 9(5)V99.
10 WS-CASE-BOOK PIC X.
10 WS-CASE-COPIES PIC 9(3).
01 WS-SUB PIC 9 VALUE ZERO.
01 WS-DECISION PIC X(28).
PROCEDURE DIVISION.
MAIN-PARA.
PERFORM VARYING WS-SUB FROM 1 BY 1 UNTIL WS-SUB > 7
MOVE WS-CASE-TYPE (WS-SUB) TO LOAN-TYPE
MOVE WS-CASE-MEMBER (WS-SUB) TO MEMBER-STATUS
MOVE WS-CASE-FINES (WS-SUB) TO MEMBER-FINES
MOVE WS-CASE-BOOK (WS-SUB) TO BOOK-STATUS
MOVE WS-CASE-COPIES (WS-SUB) TO BOOK-COPIES
PERFORM DECIDE-LOAN
DISPLAY "CASE " WS-SUB ": " WS-DECISION
END-PERFORM
STOP RUN.
DECIDE-LOAN.
EVALUATE TRUE ALSO TRUE ALSO TRUE
WHEN LOAN-RETURN ALSO ANY ALSO ANY
MOVE "ACCEPT RETURN" TO WS-DECISION
WHEN LOAN-CHECKOUT ALSO MEMBER-SUSPENDED ALSO ANY
MOVE "REJECT: MEMBER SUSPENDED" TO WS-DECISION
WHEN LOAN-CHECKOUT ALSO ANY ALSO BOOK-WITHDRAWN
MOVE "REJECT: BOOK WITHDRAWN" TO WS-DECISION
WHEN LOAN-CHECKOUT ALSO ANY ALSO BOOK-COPIES = ZERO
MOVE "REJECT: NO COPY AVAILABLE" TO WS-DECISION
WHEN LOAN-CHECKOUT ALSO MEMBER-FINES > WS-FINE-LIMIT
ALSO ANY
MOVE "HOLD: FINES OVER LIMIT" TO WS-DECISION
WHEN LOAN-CHECKOUT ALSO ANY ALSO ANY
MOVE "ACCEPT CHECKOUT" TO WS-DECISION
WHEN OTHER
MOVE "REJECT: UNKNOWN TYPE" TO WS-DECISION
END-EVALUATE.
CASE 1: ACCEPT CHECKOUT
CASE 2: REJECT: MEMBER SUSPENDED
CASE 3: HOLD: FINES OVER LIMIT
CASE 4: REJECT: BOOK WITHDRAWN
CASE 5: REJECT: NO COPY AVAILABLE
CASE 6: ACCEPT RETURN
CASE 7: REJECT: UNKNOWN TYPE
Case 6 is a suspended member who returns a book: the first WHEN accepts it before the suspension rule is ever reached.
Case 3 has fines of 12.50 against a limit of 10.00. The rows are in the order of the diagram, which is the order in which
the compiler tries them:
The diagram shows the decision order of the EVALUATE: each diamond is one WHEN phrase, the first test that passes picks the
action, and WHEN OTHER is what remains. The ANY objects are the conditions missing from a diamond. Reordering the phrases
changes the answer for a suspended member who returns a book, so the order is part of the specification. A comment next to
the statement, or a test for each row, is how that specification is kept (see
Coding Standards and Best Practices).
Pitfalls, with the real diagnostics
The program below is deliberately wrong. It contains the errors that the compiler does catch:
IDENTIFICATION DIVISION.
PROGRAM-ID. BADCOND.
DATA DIVISION.
WORKING-STORAGE SECTION.
COPY BOOKREC.
COPY LOANREC.
PROCEDURE DIVISION.
MAIN-PARA.
IF "A" = "B"
DISPLAY "C"
END-IF
EVALUATE LOAN-TYPE ALSO BOOK-STATUS
WHEN "O"
DISPLAY "D"
END-EVALUATE
EVALUATE BOOK-COPIES
WHEN "A" THRU 9
DISPLAY "E"
END-EVALUATE
IF BOOK-COPIES NOT > 5 AND NOT <
DISPLAY "F"
END-IF
STOP RUN.
Real output of cobc -x -Wall bad.cob:
bad.cob: in paragraph 'MAIN-PARA':
bad.cob:9: warning: expression 'A' EQUALS 'B' is always FALSE [-Wconstant-expression]
7 | PROCEDURE DIVISION.
8 | MAIN-PARA.
9 > IF "A" = "B"
10 | DISPLAY "C"
11 | END-IF
bad.cob:13: error: wrong number of WHEN parameters
11 | END-IF
12 | EVALUATE LOAN-TYPE ALSO BOOK-STATUS
13 > WHEN "O"
14 | DISPLAY "D"
15 | END-EVALUATE
bad.cob:17: warning: literal 'A' is alphanumeric but 'BOOK-COPIES' is numeric [-Wconstant-expression]
15 | END-EVALUATE
16 | EVALUATE BOOK-COPIES
17 > WHEN "A" THRU 9
18 | DISPLAY "E"
19 | END-EVALUATE
bad.cob:20: error: invalid expression: unfinished expression
18 | DISPLAY "E"
19 | END-EVALUATE
20 > IF BOOK-COPIES NOT > 5 AND NOT <
21 | DISPLAY "F"
22 | END-IF
-
IF "A" = "B"compares two literals; the answer is known at compile time, and the compiler says so. Such tests are usually left over from debugging or from a copybook expansion. -
An
EVALUATEwith two subjects and aWHENwith one object is an error: the objects must match the subjects one to one. -
WHEN "A" THRU 9mixes classes. Here the compiler warns and the test can never be true; Enterprise COBOL requires the two operands of aTHRUto be of the same class. -
NOT >with no operand is an incomplete expression, an error.
What the compiler does not catch matters more: the stray period, the abbreviated NOT = 1 OR 2, an ALPHABETIC test on a
numeric item, = 12.5 against an alphanumeric item, and a numeric class test on data that was never validated. The first three have
examples above; the rest need review and test cases, as does the rule of every decision table, which is one test per row.
Where the book is outdated
The book consulted for this section (Taulli, Modern Mainframe Development, 2022) was written around Enterprise COBOL 6.3 and
covers IF, class conditions and EVALUATE at the survey level it gives the rest of the language; nothing is copied from it.
What a reader should update, using the current manuals:
-
The
OMITTEDclass test and theOPTIONALphrase onBY REFERENCEparameters are documented by the 6.5 Language Reference; the section outline for this site lists them as new in 6.5, a release the book (6.3) predates. The book could not describe them. -
The book’s examples were run with GnuCOBOL (OpenCobolIDE), which accepts several things that Enterprise COBOL rejects or that compile only with an option, as shown above:
ALPHABETICon a numeric item, a non-integer literal compared with an alphanumeric item, and partial expressions inEVALUATE. A test that passes on a PC can fail the z/OS compile. -
COBOL has moved to explicit scope terminators since 1985. The period-terminated nested
IFandNEXT SENTENCEthat old examples (and programs) use survive for compatibility only; new code usesEND-IF,END-EVALUATEandCONTINUE.
References
-
Enterprise COBOL for z/OS 6.5 Language Reference: Conditional expressions
-
Enterprise COBOL for z/OS 6.5 Language Reference: Class condition
-
Enterprise COBOL for z/OS 6.5 Language Reference: Abbreviated combined relation conditions
-
Enterprise COBOL for z/OS 6.5 Language Reference: IF statement
-
Enterprise COBOL for z/OS 6.5 Language Reference: EVALUATE statement
-
Enterprise COBOL for z/OS 6.5 Language Reference: CONTINUE statement
-
Enterprise COBOL for z/OS 6.5 Programming Guide: Coding conditional expressions
-
Enterprise COBOL for z/OS 6.5 Programming Guide: Using the EVALUATE statement
-
Modern Mainframe Development (Tom Taulli, O’Reilly Media, 2022), consulted as a narrative