PERFORM and Structured Programming
|
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 upcoming 4th edition is in ballot), where the inline PERFORM and the TEST BEFORE/TEST AFTER choice come from COBOL 85, EXIT PERFORM, EXIT PARAGRAPH and EXIT SECTION from COBOL 2002 and PERFORM UNTIL EXIT is the newest addition; against IBM Enterprise COBOL for z/OS 6.5 (PTFs through September 2026), whose Language Reference documents all of them; and against GnuCOBOL 3.2 (28 July 2023, the latest release), with which every program on it was compiled and run on 2026-10-20 with cobc -x -Wall (and again with -std=ibm, which printed nothing different unless the text says so). The programs that are deliberately wrong say so, and the compiler messages shown are the ones cobc (GnuCOBOL) 3.2.0 printed.
COBOL has no for, while or function keyword. PERFORM plays all three roles: it runs a paragraph or section as a subroutine (control returns to the statement after the PERFORM), it repeats a procedure or a block of statements, and it is the only loop the language needs. This page covers its forms, the statements that leave a paragraph, a section or a loop early, and how a batch program is organized around them. Conditions are on Conditions, IF and EVALUATE; paragraphs and sections are introduced on Program Structure and Divisions; subscripts and indexes used as loop counters are on Tables and OCCURS. The examples use the Circulation public-library scenario, in particular the transaction file LOANTRAN with the copybook LOANREC.
Where the book is outdated: Taulli’s Modern Mainframe Development (2022) lists the PERFORM variants and GO TO in its language tour, in the style of the COBOL 85 programs that still dominate mainframe shops. It does not present EXIT PERFORM [CYCLE], EXIT PARAGRAPH and EXIT SECTION as the structured way out of a loop or paragraph, and it cannot cover PERFORM UNTIL EXIT, which Enterprise COBOL added in version 6.3 (through a PTF) and the 2023 standard adopted. Modern code needs neither GO TO nor ALTER for any control flow; the last section shows each old construct next to its replacement.
The PERFORM statement at a glance
| Form | What it does | Origin |
|---|---|---|
|
Runs the paragraph once and returns |
Every COBOL |
|
Runs the paragraphs from |
Every COBOL |
|
Runs every paragraph of the section, once |
Every COBOL |
|
Repeats a procedure or an inline block |
Every COBOL |
|
Repeats until the condition is true; the condition is tested first ( |
Every COBOL |
|
Counted loop that sets, steps and tests a counter; |
Every COBOL |
|
Inline form: the statements between |
COBOL 85 |
|
Leaves, or continues with the next iteration of, the innermost inline |
COBOL 2002 |
|
Loop with no end condition; only an explicit escape ends it |
COBOL 2023; Enterprise COBOL 6.3 (PTF); GnuCOBOL 3.2 |
Out-of-line and inline PERFORM
PERFORM comes in two shapes that cannot be mixed. An out-of-line PERFORM names a procedure (PERFORM OPEN-DESK): control goes to the first statement of that paragraph or section and returns to the statement after the PERFORM when the last statement of the paragraph (or of the last paragraph of the section) has run. An inline PERFORM names no procedure and carries its body between PERFORM and END-PERFORM; the body runs in place. The TIMES, UNTIL and VARYING phrases work in both shapes; the only exception is the AFTER phrase of VARYING, which the Language Reference allows only when a procedure name is given.
PERFORM procedure-name-1 [THRU procedure-name-2] [phrase]
PERFORM [phrase] imperative-statements END-PERFORM
phrase: integer-or-identifier TIMES
| [WITH TEST BEFORE|AFTER] UNTIL condition
| UNTIL EXIT
| [WITH TEST BEFORE|AFTER] VARYING counter FROM start BY step
UNTIL condition
[AFTER counter FROM start BY step UNTIL condition] ...
The rules that matter in daily work:
-
A paragraph name is the end of the performed range.
PERFORM Aruns paragraphAand nothing of the paragraph that follows it. For a section, the range is every paragraph in it. -
THRU(orTHROUGH) extends the range to the end of a second paragraph that must appear after the first. Ranges built withTHRUare discouraged (see the section on structured style); a section is the better way to group paragraphs. -
A paragraph that is reached by falling down from the paragraph above runs without any
PERFORMbeing involved: control "passes through the exit point to the next executable statement" as if noPERFORMreferred to the paragraph. This is the commonest beginner bug (see the pitfalls). -
Performed ranges can nest, but the inner range must lie completely inside or completely outside the outer one. A
PERFORMmust not cause itself to be executed: the Language Reference calls a recursivePERFORMunpredictable, and GnuCOBOL 3.2 compiled such a program without a word. Recursion belongs to programs declaredRECURSIVEand toCALL, not toPERFORM. -
TIMEStakes a literal or an integer item, at most 999,999,999 in Enterprise COBOL. If the item is zero or negative the body is skipped, and changing the item inside the body does not change the number of iterations. -
The
INLINEcompiler directive in Enterprise COBOL lets the compiler decide whether a performed procedure may be inlined; it never changes what the program does.
The first program runs every basic form, one after the other, on the opening and closing duties of the library desk.
IDENTIFICATION DIVISION.
PROGRAM-ID. PERFFORMS.
* Every basic form of PERFORM, with the Circulation desk as theme.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 WS-COUNT PIC 9(2) VALUE 0.
01 WS-SHELF PIC 9(1).
01 WS-SLOT PIC 9(1).
01 WS-N PIC 9(1) VALUE 3.
PROCEDURE DIVISION.
MAIN-LINE.
DISPLAY "1. paragraph:"
PERFORM OPEN-DESK
DISPLAY "2. THRU range:"
PERFORM OPEN-DESK THRU OPEN-DESK-EXIT
DISPLAY "3. section:"
PERFORM CLOSING-DUTIES
DISPLAY "4. TIMES:"
PERFORM STAMP-DUE-SLIP 2 TIMES
DISPLAY "5. inline TIMES, count changed inside:"
PERFORM WS-N TIMES
ADD 1 TO WS-COUNT
MOVE 1 TO WS-N
END-PERFORM
DISPLAY " iterations=" WS-COUNT
DISPLAY "6. UNTIL (test before):"
MOVE 5 TO WS-COUNT
PERFORM UNTIL WS-COUNT >= 3
ADD 1 TO WS-COUNT
END-PERFORM
DISPLAY " count=" WS-COUNT
DISPLAY "7. WITH TEST AFTER:"
PERFORM WITH TEST AFTER UNTIL WS-COUNT >= 3
ADD 1 TO WS-COUNT
END-PERFORM
DISPLAY " count=" WS-COUNT
DISPLAY "8. VARYING:"
PERFORM VARYING WS-SHELF FROM 1 BY 1 UNTIL WS-SHELF > 3
DISPLAY " shelf " WS-SHELF
END-PERFORM
DISPLAY " after loop shelf=" WS-SHELF
DISPLAY "9. VARYING ... AFTER (slot is the inner loop):"
PERFORM SHOW-SLOT
VARYING WS-SHELF FROM 1 BY 1 UNTIL WS-SHELF > 2
AFTER WS-SLOT FROM 1 BY 1 UNTIL WS-SLOT > 3
STOP RUN.
OPEN-DESK.
DISPLAY " switch on the terminal".
OPEN-DESK-EXIT.
EXIT.
CLOSING-DUTIES SECTION.
COUNT-CASH.
DISPLAY " count the cash drawer".
LOCK-DOORS.
DISPLAY " lock the doors".
SHOW-SLOT.
DISPLAY " shelf " WS-SHELF " slot " WS-SLOT.
SLIPS SECTION.
STAMP-DUE-SLIP.
DISPLAY " stamp a due-date slip".
Output (compiled with cobc -x -Wall, no warnings; -std=ibm printed the same):
1. paragraph:
switch on the terminal
2. THRU range:
switch on the terminal
3. section:
count the cash drawer
lock the doors
shelf 0 slot 0
4. TIMES:
stamp a due-date slip
stamp a due-date slip
5. inline TIMES, count changed inside:
iterations=03
6. UNTIL (test before):
count=05
7. WITH TEST AFTER:
count=06
8. VARYING:
shelf 1
shelf 2
shelf 3
after loop shelf=4
9. VARYING ... AFTER (slot is the inner loop):
shelf 1 slot 1
shelf 1 slot 2
shelf 1 slot 3
shelf 2 slot 1
shelf 2 slot 2
shelf 2 slot 3
Three details of that output are worth remembering. In case 5 the counter WS-N was changed to 1 inside the body, yet the body still ran three times: TIMES evaluates its count once, when the statement starts. In cases 6 and 7 the condition was already true when the loop was reached (the counter was 5, and later 6, against a limit of 3): with TEST BEFORE the body never ran, with TEST AFTER it ran once, so WITH TEST AFTER is COBOL’s do-while loop. And in case 8 the counter ends at 4, not 3: VARYING adds the step and then tests, so on exit the counter holds the first value that satisfied the condition.
VARYING and the AFTER phrases
PERFORM VARYING sets the counter to the FROM value, tests the UNTIL condition, runs the body, adds the BY value and tests again. Each AFTER phrase adds a counter that runs through its complete cycle for every step of the counter before it, so case 9 above is a pair of nested loops: WS-SLOT is the inner one. The Language Reference gives the exact sequence for two and three counters, and notes that the format can serially search an entire seven-dimensional table. At the end, each AFTER counter holds its FROM value again, and the first counter holds the value that ended the loop (or its FROM value if the condition was true from the start). The counters can be subscripts, index-names or numeric items, and a subscripted counter or condition operand is re-evaluated at every test.
The counter must be able to reach the value that satisfies the condition. A PIC 9(1) counter in UNTIL WS-I > 9 can never exceed 9: adding 1 to 9 truncates to 0 and the loop starts again. GnuCOBOL 3.2 recognizes this and warns at compile time (the loop below carries a second condition only so that the demonstration stops):
IDENTIFICATION DIVISION.
PROGRAM-ID. VARYEND.
* Where the varied item stands after the loop, and a loop whose
* counter is too small to ever pass the limit (guarded here).
DATA DIVISION.
WORKING-STORAGE SECTION.
01 WS-I PIC 9(1).
01 WS-GUARD PIC 9(2) VALUE 0.
PROCEDURE DIVISION.
MAIN-LINE.
PERFORM VARYING WS-I FROM 1 BY 1 UNTIL WS-I > 9
OR WS-GUARD >= 12
ADD 1 TO WS-GUARD
END-PERFORM
DISPLAY "guard=" WS-GUARD " i=" WS-I
STOP RUN.
Compiled with cobc -x -Wall (the message is identical with -std=ibm), then run:
vary.cob: in paragraph 'MAIN-LINE':
vary.cob:11: warning: 'WS-I' may not be GREATER THAN 9 [-Wconstant-expression]
9 | PROCEDURE DIVISION.
10 | MAIN-LINE.
11 > PERFORM VARYING WS-I FROM 1 BY 1 UNTIL WS-I > 9
12 | OR WS-GUARD >= 12
13 | ADD 1 TO WS-GUARD
guard=12 i=3
The counter went 1 to 9, wrapped to 0, and the guard stopped the loop on the twelfth iteration, leaving WS-I at 3. Without the guard the program would never end. Size the counter one digit larger than the largest value in the condition.
Source: GnuCOBOL 3.2 Manual
Leaving early: the EXIT statements
COBOL 2002 added statements that leave a paragraph, a section or an inline loop without GO TO:
| Statement | Effect |
|---|---|
|
Leaves the current paragraph at once; the statements after it in the paragraph do not run. Control moves to the end of the paragraph, so a |
|
Leaves the current section at once, skipping the remaining statements and paragraphs of the section. |
|
Only inside an inline |
|
Only inside an inline |
|
A loop whose condition never becomes true. It must contain a statement that really escapes it. |
|
The old "format 1" |
EXIT PERFORM outside an inline PERFORM is a compile-time error. EXIT PROGRAM and EXIT METHOD are different statements that end a called program or a method; they are covered with CALL on Subprograms, CALL and Linkage. Enterprise COBOL does not support EXIT FUNCTION.
The program below reports the fines of four members stored in a small table, using each form once.
IDENTIFICATION DIVISION.
PROGRAM-ID. PERFEXITS.
* EXIT PARAGRAPH, EXIT SECTION, EXIT PERFORM [CYCLE] and
* PERFORM UNTIL EXIT, using member fines from the Circulation desk.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 WS-FINE-TABLE.
05 WS-FINE-VALUES.
10 FILLER PIC 9(3)V99 VALUE 0.
10 FILLER PIC 9(3)V99 VALUE 12.50.
10 FILLER PIC 9(3)V99 VALUE 0.
10 FILLER PIC 9(3)V99 VALUE 50.00.
05 WS-FINE REDEFINES WS-FINE-VALUES
PIC 9(3)V99 OCCURS 4 TIMES.
01 WS-IX PIC 9(1).
01 WS-TRIES PIC 9(1) VALUE 0.
01 WS-SHOW PIC ZZ9.99.
PROCEDURE DIVISION.
MAIN-LINE.
DISPLAY "A. EXIT PARAGRAPH:"
MOVE 2 TO WS-IX
PERFORM REPORT-ONE-FINE
MOVE 1 TO WS-IX
PERFORM REPORT-ONE-FINE
DISPLAY "B. EXIT SECTION:"
PERFORM CHECK-DESK
DISPLAY "C. EXIT PERFORM CYCLE (skip zero) / EXIT PERFORM:"
PERFORM VARYING WS-IX FROM 1 BY 1 UNTIL WS-IX > 4
IF WS-FINE (WS-IX) = 0
EXIT PERFORM CYCLE
END-IF
IF WS-FINE (WS-IX) > 40
DISPLAY " member " WS-IX " over limit, stop"
EXIT PERFORM
END-IF
MOVE WS-FINE (WS-IX) TO WS-SHOW
DISPLAY " member " WS-IX " owes " WS-SHOW
END-PERFORM
DISPLAY " loop ended with index " WS-IX
DISPLAY "D. PERFORM UNTIL EXIT:"
PERFORM UNTIL EXIT
ADD 1 TO WS-TRIES
DISPLAY " attempt " WS-TRIES
IF WS-TRIES = 3
EXIT PERFORM
END-IF
END-PERFORM
DISPLAY " done"
STOP RUN.
REPORT-ONE-FINE.
IF WS-FINE (WS-IX) = 0
DISPLAY " member " WS-IX " owes nothing"
EXIT PARAGRAPH
END-IF
MOVE WS-FINE (WS-IX) TO WS-SHOW
DISPLAY " member " WS-IX " owes " WS-SHOW.
CHECK-DESK SECTION.
CHECK-LIGHTS.
DISPLAY " lights checked"
IF WS-TRIES = 0
EXIT SECTION
END-IF
DISPLAY " (not reached on the first call)".
CHECK-DOORS.
DISPLAY " doors checked (not reached either)".
Output (cobc -x -Wall printed no warnings; -std=ibm printed the same):
A. EXIT PARAGRAPH:
member 2 owes 12.50
member 1 owes nothing
B. EXIT SECTION:
lights checked
C. EXIT PERFORM CYCLE (skip zero) / EXIT PERFORM:
member 2 owes 12.50
member 4 over limit, stop
loop ended with index 4
D. PERFORM UNTIL EXIT:
attempt 1
attempt 2
attempt 3
done
In part C, EXIT PERFORM CYCLE skipped the members who owe nothing and EXIT PERFORM ended the loop at the first fine over the limit; the index stayed at 4 because the VARYING step does not run after EXIT PERFORM. In part B EXIT SECTION skipped the rest of CHECK-DESK, including the whole second paragraph.
PERFORM UNTIL EXIT
PERFORM UNTIL EXIT behaves exactly like PERFORM UNTIL condition with a condition that is never true. The loop therefore needs an escape inside it. The rules from the Language Reference:
-
UNTIL EXITmust not be combined withTEST BEFORE,TEST AFTERorVARYING. GnuCOBOL 3.2 rejects it withVARYING(error: syntax error, unexpected EXIT) but accepts it silently withWITH TEST AFTER, so do not rely on the compiler to find that mistake. -
For an inline loop, the escape is
EXIT PERFORM.EXIT PERFORM CYCLEis not an escape. -
For an out-of-line loop, the escape is
GOBACKorSTOP RUN.EXIT PARAGRAPHinside the performed paragraph andEXIT SECTIONinside the performed section do not escape the loop: they only end that pass through the paragraph or section, and the loop starts again.
Where it helps: a read loop that has to test the result of the READ before using the record, without a priming read (see the next section), and loops with several unrelated exit conditions, each of which becomes an IF ... EXIT PERFORM.
The batch skeleton
Almost every batch program in the Circulation back office has the same shape: initialize, process the transactions until end of file, terminate. The paragraphs are named with a number prefix that fixes their order on the listing, one paragraph does one job, and the main line only performs paragraphs.
The first read happens before the loop (the priming read) so that the loop test always has a record, or the end-of-file switch, to look at. The last statement of the processing paragraph reads the next record, so the loop test at the top sees it. The same READ paragraph serves both places. The program counts the day’s transactions on LOANTRAN.
IDENTIFICATION DIVISION.
PROGRAM-ID. LOANBATCH.
* Batch skeleton: initialize, priming read, process until end of
* file, terminate. Counts the day's transactions on LOANTRAN.
ENVIRONMENT DIVISION.
INPUT-OUTPUT SECTION.
FILE-CONTROL.
SELECT LOAN-FILE ASSIGN TO "LOANTRAN"
ORGANIZATION IS LINE SEQUENTIAL.
DATA DIVISION.
FILE SECTION.
FD LOAN-FILE.
COPY LOANREC.
WORKING-STORAGE SECTION.
01 WS-SWITCHES.
05 WS-EOF-SW PIC X VALUE "N".
88 END-OF-LOANS VALUE "Y".
01 WS-COUNTS.
05 WS-READ-CNT PIC 9(4) VALUE 0.
05 WS-CHECKOUT-CNT PIC 9(4) VALUE 0.
05 WS-RETURN-CNT PIC 9(4) VALUE 0.
05 WS-BAD-CNT PIC 9(4) VALUE 0.
PROCEDURE DIVISION.
0000-MAIN-LINE.
PERFORM 1000-INITIALIZE
PERFORM 2000-PROCESS-LOAN UNTIL END-OF-LOANS
PERFORM 3000-TERMINATE
STOP RUN.
1000-INITIALIZE.
OPEN INPUT LOAN-FILE
PERFORM 8000-READ-LOAN.
2000-PROCESS-LOAN.
EVALUATE TRUE
WHEN LOAN-CHECKOUT
ADD 1 TO WS-CHECKOUT-CNT
WHEN LOAN-RETURN
ADD 1 TO WS-RETURN-CNT
WHEN OTHER
ADD 1 TO WS-BAD-CNT
END-EVALUATE
PERFORM 8000-READ-LOAN.
3000-TERMINATE.
CLOSE LOAN-FILE
DISPLAY "records read " WS-READ-CNT
DISPLAY "checkouts " WS-CHECKOUT-CNT
DISPLAY "returns " WS-RETURN-CNT
DISPLAY "rejected " WS-BAD-CNT.
8000-READ-LOAN.
READ LOAN-FILE
AT END
SET END-OF-LOANS TO TRUE
NOT AT END
ADD 1 TO WS-READ-CNT
END-READ.
Output (cobc -x -Wall printed no warnings; -std=ibm printed the same). LOANTRAN holds three checkouts and one return:
records read 0004
checkouts 0003
returns 0001
rejected 0000
The same job without a priming read puts the READ inside a PERFORM UNTIL EXIT loop and leaves it with EXIT PERFORM when the end-of-file switch is set. Only the main line and the two paragraphs that lost their READ differ from the first version:
IDENTIFICATION DIVISION.
PROGRAM-ID. LOANBATCH2.
* The same job without a priming read: one READ inside a
* PERFORM UNTIL EXIT loop (COBOL 2023; Enterprise COBOL 6.3+).
ENVIRONMENT DIVISION.
INPUT-OUTPUT SECTION.
FILE-CONTROL.
SELECT LOAN-FILE ASSIGN TO "LOANTRAN"
ORGANIZATION IS LINE SEQUENTIAL.
DATA DIVISION.
FILE SECTION.
FD LOAN-FILE.
COPY LOANREC.
WORKING-STORAGE SECTION.
01 WS-SWITCHES.
05 WS-EOF-SW PIC X VALUE "N".
88 END-OF-LOANS VALUE "Y".
01 WS-COUNTS.
05 WS-READ-CNT PIC 9(4) VALUE 0.
05 WS-CHECKOUT-CNT PIC 9(4) VALUE 0.
05 WS-RETURN-CNT PIC 9(4) VALUE 0.
05 WS-BAD-CNT PIC 9(4) VALUE 0.
PROCEDURE DIVISION.
0000-MAIN-LINE.
PERFORM 1000-INITIALIZE
PERFORM UNTIL EXIT
PERFORM 8000-READ-LOAN
IF END-OF-LOANS
EXIT PERFORM
END-IF
PERFORM 2000-PROCESS-LOAN
END-PERFORM
PERFORM 3000-TERMINATE
STOP RUN.
1000-INITIALIZE.
OPEN INPUT LOAN-FILE.
2000-PROCESS-LOAN.
EVALUATE TRUE
WHEN LOAN-CHECKOUT
ADD 1 TO WS-CHECKOUT-CNT
WHEN LOAN-RETURN
ADD 1 TO WS-RETURN-CNT
WHEN OTHER
ADD 1 TO WS-BAD-CNT
END-EVALUATE.
3000-TERMINATE.
CLOSE LOAN-FILE
DISPLAY "records read " WS-READ-CNT
DISPLAY "checkouts " WS-CHECKOUT-CNT
DISPLAY "returns " WS-RETURN-CNT
DISPLAY "rejected " WS-BAD-CNT.
8000-READ-LOAN.
READ LOAN-FILE
AT END
SET END-OF-LOANS TO TRUE
NOT AT END
ADD 1 TO WS-READ-CNT
END-READ.
It prints exactly the same four lines:
records read 0004
checkouts 0003
returns 0001
rejected 0000
Source: GnuCOBOL 3.2 Manual
Which style to use is mostly a matter of team convention. The priming-read version works on every compiler ever written and is what a maintenance programmer on a mainframe expects; the UNTIL EXIT version has one READ instead of two and keeps the end-of-file test next to the read, but needs Enterprise COBOL 6.3 with the PTF or later, or a 2023-standard compiler.
Structure rules for the skeleton
-
The main line contains
PERFORMstatements and oneSTOP RUN(orGOBACKin a subprogram) and nothing else of substance. Because the paragraphs follow the main line, the end of the main line must be an explicitSTOP RUN: without it control falls into the first performed paragraph. -
One period per paragraph, at its end. Inside, use the scope terminators (
END-IF,END-EVALUATE,END-READ,END-PERFORM) so that a stray period cannot silently close a statement early. -
Give a paragraph one entry and one exit: it is entered by a
PERFORMand left at its last statement. UseEXIT PARAGRAPHto leave early rather than aGO TOto an exit paragraph. -
Keep paragraph names unique and numbered or prefixed by area, so a cross-reference listing is readable; sections are optional and only worth having when a group of paragraphs is performed as a unit.
-
Put the end-of-file test in a level 88 condition name (
END-OF-LOANS), set by theAT ENDbranch of theREAD.
Pitfall: falling through into a paragraph
The main line below has no STOP RUN. This program is wrong on purpose:
IDENTIFICATION DIVISION.
PROGRAM-ID. FALLTHRU.
* Deliberately wrong: MAIN-LINE has no STOP RUN or GOBACK.
PROCEDURE DIVISION.
MAIN-LINE.
DISPLAY "main line"
PERFORM PRINT-REPORT.
PRINT-REPORT.
DISPLAY "report printed".
It compiles without a message and prints:
main line
report printed
report printed
PRINT-REPORT ran once because of the PERFORM, and a second time because MAIN-LINE ended and control simply moved on to the next paragraph. GnuCOBOL 3.2 gives no diagnostic; in a real program the second run might add every record twice. The fix is STOP RUN (or GOBACK) as the last statement of the main line.
Pitfall: compiler diagnostics for malformed PERFORMs
The messages below are what cobc -x -Wall printed for four small invalid programs, shortened to the line that matters:
error: PERFORM statement not terminated by END-PERFORM
PERFORM UNTIL WS-I > 2
ADD 1 TO WS-I.
error: EXIT PERFORM is only valid with inline PERFORM
EXIT PERFORM
error: syntax error, unexpected ADD, expecting UNTIL or VARYING
PERFORM WITH TEST BEFORE
ADD 1 TO WS-I
warning: EARLY-PARA is defined before LATE-PARA [-Wsuspicious-perform-thru]
PERFORM LATE-PARA THRU EARLY-PARA
The first is the period inside an inline PERFORM: the period ends the whole statement, so the END-PERFORM is missing. The last one is a THRU range whose end comes before its start in the source.
Structured style
Structured programming in COBOL comes down to a few habits, most of them already visible in the skeleton:
-
Control flow uses only sequence,
IF/EVALUATEandPERFORM; noGO TO, noALTER. -
Every performed paragraph has a single purpose and a name that says it (
2100-APPLY-CHECKOUT, notPARA-17). -
PERFORM ... THRUranges, and the exit paragraphs that go with them, are replaced by single paragraphs or by sections. A range depends on the physical order of the paragraphs, so inserting a paragraph in the wrong place changes what runs. -
Loops use the inline form when the body is a few statements and an out-of-line paragraph when it is long or reused.
-
Exits from loops use
EXIT PERFORM,EXIT PERFORM CYCLEandEXIT PARAGRAPHinstead of jumping out.
Old code you may meet: GO TO, GO TO DEPENDING ON and ALTER
Programs written before 1985 are built from GO TO. They still run in production, and you will be asked to read them. The unconditional GO TO paragraph transfers control and never returns; GO TO p1 p2 p3 DEPENDING ON n jumps to the n-th named paragraph, and falls through to the next statement when n is outside 1 to 3; ALTER paragraph TO PROCEED TO other rewrites, at run time, the target of a paragraph that consists of a single GO TO, so what a GO TO does depends on which statements ran earlier. The 85 standard classed ALTER as obsolete and the 2002 standard dropped it; Enterprise COBOL still accepts it, but not in a RECURSIVE program (nor an altered GO TO in a program compiled with the THREAD option). Do not write any of this in new code.
The program below is old code you may meet; it is deliberately a tangle. The compiler accepts it with a warning about ALTER (warning: ALTER is obsolete in GnuCOBOL [-Wobsolete], worded in IBM COBOL (lax) under -std=ibm):
IDENTIFICATION DIVISION.
PROGRAM-ID. OLDSTYLE.
* OLD CODE YOU MAY MEET: GO TO, GO TO DEPENDING ON and ALTER.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 WS-CHOICE PIC 9 VALUE 2.
01 WS-TRAN-TYPE PIC X VALUE "R".
PROCEDURE DIVISION.
MAIN-LINE.
GO TO CHECKOUT-PARA RETURN-PARA RENEW-PARA
DEPENDING ON WS-CHOICE.
DISPLAY "choice out of range".
GO TO FIRST-ACTION.
CHECKOUT-PARA.
DISPLAY "checkout".
GO TO FIRST-ACTION.
RETURN-PARA.
DISPLAY "return".
GO TO FIRST-ACTION.
RENEW-PARA.
DISPLAY "renew".
GO TO FIRST-ACTION.
FIRST-ACTION.
GO TO FIRST-TIME.
FIRST-TIME-DONE.
PERFORM LATER-ACTION.
STOP RUN.
FIRST-TIME.
DISPLAY "first-time work".
ALTER FIRST-ACTION TO PROCEED TO FIRST-TIME-DONE.
GO TO FIRST-ACTION.
LATER-ACTION.
DISPLAY "later work".
old.cob: in paragraph 'FIRST-TIME':
old.cob:30: warning: ALTER is obsolete in GnuCOBOL [-Wobsolete]
28 | FIRST-TIME.
29 | DISPLAY "first-time work".
30 > ALTER FIRST-ACTION TO PROCEED TO FIRST-TIME-DONE.
31 | GO TO FIRST-ACTION.
32 | LATER-ACTION.
return
first-time work
later work
Its modern form is an EVALUATE for the choice and a condition name for the once-only work, with PERFORM for the subroutine; it needs no GO TO and no ALTER, compiles without warnings and prints the same three lines:
IDENTIFICATION DIVISION.
PROGRAM-ID. MODERN.
* The modern form of OLDSTYLE: EVALUATE and a condition-name.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 WS-CHOICE PIC 9 VALUE 2.
01 WS-FIRST-SW PIC X VALUE "Y".
88 FIRST-TIME VALUE "Y".
PROCEDURE DIVISION.
MAIN-LINE.
EVALUATE WS-CHOICE
WHEN 1
DISPLAY "checkout"
WHEN 2
DISPLAY "return"
WHEN 3
DISPLAY "renew"
WHEN OTHER
DISPLAY "choice out of range"
END-EVALUATE
IF FIRST-TIME
DISPLAY "first-time work"
MOVE "N" TO WS-FIRST-SW
END-IF
PERFORM LATER-ACTION
STOP RUN.
LATER-ACTION.
DISPLAY "later work".
return
first-time work
later work
Source: GnuCOBOL 3.2 Manual
When you must change such a program, the safe course is to leave its control flow alone, change only the paragraph you need, and replace the tangle with PERFORM and EVALUATE as a separate, tested step. The migration side of this (how to find ALTER and GO TO DEPENDING ON in a large code base and what to replace them with) is covered on Legacy Features and Migration.
Standard, Enterprise COBOL and GnuCOBOL
| Feature | ISO/IEC 1989:2023 | Enterprise COBOL 6.5 | GnuCOBOL 3.2 |
|---|---|---|---|
Inline |
Yes (COBOL 85) |
Yes |
Yes |
|
Yes (COBOL 2002) |
Yes |
Yes; rejected by |
|
Yes (2023) |
Yes, since 6.3 (PTF) |
Yes |
|
Not allowed |
Not allowed |
|
|
Language Reference of 6.5: not allowed |
Not allowed |
Accepted |
Recursive |
Undefined |
Unpredictable; no diagnostic documented |
No diagnostic |
Source: GnuCOBOL 3.2 Manual