Hard-coded tables, INDEXED BY and SET
Some tables never change: month names, days per month, state codes. Loading them from input would be overkill, so they are written into the program.
Hard-coding a table with REDEFINES
Traditionally you cannot give each element of an OCCURS table its own
VALUE. The classic workaround, found in almost every legacy program, is to
lay the values out as ordinary FILLER fields, then redefine the same
bytes as a table:
IDENTIFICATION DIVISION.
PROGRAM-ID. IDXDEMO.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 WS-DAY-VALUES.
05 FILLER PIC X(3) VALUE "MON".
05 FILLER PIC X(3) VALUE "TUE".
05 FILLER PIC X(3) VALUE "WED".
05 FILLER PIC X(3) VALUE "THU".
05 FILLER PIC X(3) VALUE "FRI".
01 WS-DAY-TABLE REDEFINES WS-DAY-VALUES.
05 WS-DAY-NAME PIC X(3) OCCURS 5 TIMES
INDEXED BY DAY-IX.
01 WS-POS PIC 9(2).
PROCEDURE DIVISION.
SET DAY-IX TO 1
DISPLAY "FIRST: " WS-DAY-NAME(DAY-IX)
SET DAY-IX UP BY 2
DISPLAY "THIRD: " WS-DAY-NAME(DAY-IX)
SET DAY-IX DOWN BY 1
SET WS-POS TO DAY-IX
DISPLAY "POSITION " WS-POS ": " WS-DAY-NAME(DAY-IX)
STOP RUN.
Output:
FIRST: MON
THIRD: WED
POSITION 02: TUE
REDEFINES means "these are the same bytes, described differently". The
15 bytes MONTUEWEDTHUFRI are both five FILLERs and a five-element table.
The two layouts must be the same length.
Values can be packed several to a FILLER, and elements can be groups:
01 WS-MONTH-VALUES.
05 FILLER PIC X(15) VALUE "JAN31FEB28MAR31".
01 WS-MONTH-TABLE REDEFINES WS-MONTH-VALUES.
05 WS-MONTH OCCURS 3 TIMES INDEXED BY MON-IX.
10 WS-MON-NAME PIC X(3).
10 WS-MON-DAYS PIC 9(2).
Here WS-MON-DAYS(2) is 28: digits in a text literal are valid
display-numeric data once redefined as PIC 9.
Indexes: INDEXED BY
INDEXED BY DAY-IX declares an index that belongs to the table. You do
not define DAY-IX in WORKING-STORAGE. The clause creates it. Use it
wherever a subscript would go.
Why a separate kind of pointer? A subscript is an occurrence number
(1, 2, 3…), and every reference has to multiply it by the element length. On
IBM mainframes an index holds the byte offset itself, so no
multiplication is needed. It was a significant speed-up on 1970s hardware,
and SEARCH (next lesson) requires an index.
SET: the only way to change an index
| Statement | Effect |
|---|---|
SET DAY-IX TO 1 |
Point at occurrence 1 |
SET DAY-IX TO WS-START |
Point at the occurrence in a numeric field |
SET DAY-IX UP BY 2 |
Move forward two occurrences |
SET DAY-IX DOWN BY 1 |
Move back one |
SET WS-POS TO DAY-IX |
Copy the occurrence number into a numeric field |
PERFORM VARYING DAY-IX FROM 1 BY 1 UNTIL DAY-IX > 5 also works, and
comparing an index with a number (IF MON-IX = 12) compares occurrence
numbers.
Not MOVE, not ADD
An index is not an ordinary number. IBM Enterprise COBOL rejects
MOVE, ADD or DISPLAY on an index name, so use SET, and
SET a numeric field from the index when you need to display it.
GnuCOBOL is more lenient, but code written that way will not compile
on the mainframe.
On the job
Shops name indexes with an -IX or -IDX suffix and subscripts with
-SUB, so a reader knows at a glance which is which. Mixing them up
(using an index from one table on another table with a different
element length) is a classic bug, because the offset is wrong for the
other table.
Your task
A finance company needs the months of a repayment plan. Read two lines:
- The month of the first payment,
1to12 - The number of payments, e.g.
4
Display one line per payment with the month name and its number of days (ignore leap years):
PAYMENT 01: NOV (30 DAYS)
PAYMENT 02: DEC (31 DAYS)
PAYMENT 03: JAN (31 DAYS)
PAYMENT 04: FEB (28 DAYS)
After December the plan wraps round to January. If the first month is not
1–12, display only INVALID MONTH.
The starter has the first three months of the table. Complete the
hard-coded table (all 12 months, REDEFINES with VALUE), give it an index
with INDEXED BY MON-IX, and move through it with SET.
Days per month: JAN 31, FEB 28, MAR 31, APR 30, MAY 31, JUN 30, JUL 31, AUG 31, SEP 30, OCT 31, NOV 30, DEC 31.