PERFORM UNTIL, TEST BEFORE and TEST AFTER
Most batch loops do not know in advance how many times they will run. They keep going until something happens: the input runs out, a balance hits a target, a user gets their PIN right.
IDENTIFICATION DIVISION.
PROGRAM-ID. UNTILDEMO.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 WS-BALANCE PIC 9(5)V99 VALUE 100.00.
01 WS-MONTHS PIC 9(3) VALUE ZERO.
PROCEDURE DIVISION.
PERFORM UNTIL WS-BALANCE >= 150.00
COMPUTE WS-BALANCE ROUNDED = WS-BALANCE * 1.10
ADD 1 TO WS-MONTHS
END-PERFORM
DISPLAY "MONTHS: " WS-MONTHS " BALANCE: " WS-BALANCE
STOP RUN.
Output: MONTHS: 005 BALANCE: 00161.05.
Read UNTIL carefully: the loop runs while the condition is false and
stops as soon as it becomes true. If you come from another language, this is
the opposite of while. Getting it backwards is a classic first-week bug.
The out-of-line form works the same way: PERFORM 2000-PROCESS UNTIL
WS-EOF = "Y".
TEST BEFORE and TEST AFTER
By default COBOL checks the condition before each pass, so the body may
run zero times. WITH TEST AFTER checks it at the end of each pass, so the
body always runs at least once:
IDENTIFICATION DIVISION.
PROGRAM-ID. TESTDEMO.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 WS-N PIC 9(2) VALUE 5.
PROCEDURE DIVISION.
PERFORM WITH TEST BEFORE UNTIL WS-N > 3
DISPLAY "BEFORE: " WS-N
END-PERFORM
PERFORM WITH TEST AFTER UNTIL WS-N > 3
DISPLAY "AFTER: " WS-N
END-PERFORM
STOP RUN.
The condition is already true, so this prints only AFTER: 05.
TEST BEFORE is the default and is almost never written out.
TEST AFTER suits "do it, then decide whether to go again", such as an
ATM PIN prompt:
PERFORM WITH TEST AFTER
UNTIL WS-PIN = "4321" OR WS-TRIES = 3
ACCEPT WS-PIN
ADD 1 TO WS-TRIES
END-PERFORM
The priming read
The most important loop in batch COBOL reads input until a terminator. The standard shape is the priming read (or read-ahead): read the first record before the loop, and read the next one as the last step of the body.
ACCEPT WS-AMOUNT
PERFORM UNTIL WS-AMOUNT = ZERO
ADD WS-AMOUNT TO WS-TOTAL
ACCEPT WS-AMOUNT
END-PERFORM
Why not read at the top of the body? Then the terminator itself (the 0) would
be processed as if it were data before the UNTIL could stop the loop. With
the priming read, the condition is always tested against a record that has
not been processed yet. You will use this shape every time you read a
file.
Infinite loops
If nothing inside the body can make the condition true, the loop never ends. On a mainframe the job runs until it hits its CPU time limit and is cancelled with an S322 abend, often after burning expensive CPU time. Always check that the body moves you towards the exit.
On the job
Loop conditions are usually written with an 88-level flag:
PERFORM UNTIL END-OF-FILE. That reads like English and keeps the
terminating value in one place.
Your task
A branch keys in the day's deposits, one amount per line, and ends the
list with a line containing 0. Write a program that reads the amounts
and displays how many deposits there were and their total:
DEPOSITS: 003
TOTAL: 1375.50
The terminating 0 is not a deposit and must not be counted. If the first
line is 0, there were no deposits.
Use PERFORM UNTIL with a priming read: read the first amount before the
loop, and read the next one at the end of the loop body.