MODULE 11 · SUBPROGRAMS AND COPYBOOKS · 3/8

Nested programs and END PROGRAM

15 min30 XPExercise

On a mainframe, a subprogram is normally its own source member, compiled into its own load module. COBOL also lets you put a called program inside its caller's source. That is a nested program, and it is how you will write both sides of a CALL in one editor.

The shape

A nested program is a complete program (its own IDENTIFICATION, DATA and PROCEDURE divisions) placed after the containing program's procedure code and before the containing program's END PROGRAM:

IDENTIFICATION DIVISION.
PROGRAM-ID. OUTER.
   ... OUTER's data and procedure code ...
   IDENTIFICATION DIVISION.
   PROGRAM-ID. INNER.
      ... INNER's data and procedure code ...
   END PROGRAM INNER.
END PROGRAM OUTER.

END PROGRAM name. is required as soon as a source holds more than one program; it tells the compiler where each one stops. Leave one out and GnuCOBOL reports multiple PROGRAM-ID's without matching END PROGRAM. END PROGRAM is not a statement that runs. It's a boundary marker, and it starts in Area A.

Each program has its own data

A nested program cannot see its parent's WORKING-STORAGE, and the parent cannot see the nested program's. Data moves between them through the CALL ... USING parameter list, exactly as with a separately compiled subprogram. (A parent item declared GLOBAL is visible to nested programs, but shops avoid it for the same reason other languages avoid global variables.)

WORKING-STORAGE survives between calls

A called program's WORKING-STORAGE is set up once, from its VALUE clauses, the first time it is called. On later calls it still holds whatever the previous call left there. If you want a fresh copy on every call, declare the program IS INITIAL:

       IDENTIFICATION DIVISION.
       PROGRAM-ID. NESTDEMO.
       PROCEDURE DIVISION.
           CALL "TALLY"
           CALL "TALLY"
           CALL "FRESH"
           CALL "FRESH"
           STOP RUN.

       IDENTIFICATION DIVISION.
       PROGRAM-ID. TALLY.
       DATA DIVISION.
       WORKING-STORAGE SECTION.
       01  WS-CALLS         PIC 9(3) VALUE 0.
       PROCEDURE DIVISION.
           ADD 1 TO WS-CALLS
           DISPLAY "TALLY CALL " WS-CALLS
           GOBACK.
       END PROGRAM TALLY.

       IDENTIFICATION DIVISION.
       PROGRAM-ID. FRESH IS INITIAL.
       DATA DIVISION.
       WORKING-STORAGE SECTION.
       01  WS-CALLS         PIC 9(3) VALUE 0.
       PROCEDURE DIVISION.
           ADD 1 TO WS-CALLS
           DISPLAY "FRESH CALL " WS-CALLS
           GOBACK.
       END PROGRAM FRESH.
       END PROGRAM NESTDEMO.
TALLY CALL 001
TALLY CALL 002
FRESH CALL 001
FRESH CALL 001

Both nested programs declare a field called WS-CALLS. That's fine: each program's names are private to it.

Persistent storage is useful (a routine can load a rate table on its first call and reuse it) and dangerous (a flag set on one call is still set on the next). If a subprogram's results depend on the previous call, check what it initialises and when.

Who can call a nested program?

Only the program that directly contains it. In the demo above, TALLY could not call FRESH: they are siblings, and the call fails at run time with module 'FRESH' not found. Adding IS COMMON to a nested program's PROGRAM-ID lets its siblings call it too.

On the job

Nested programs are uncommon in production code; most shops keep one program per member so each can be compiled, promoted and reused on its own. You will still meet them in older systems, and they are handy for private helper routines that nothing else should call.

Your task

The starter's main program LOANINT reads loan accounts and calls CALCINT for each one, but CALCINT doesn't exist yet. Write it as a nested program inside LOANINT, before END PROGRAM LOANINT.

LOANINT calls it like this:

           CALL "CALCINT" USING WS-BALANCE WS-RATE
                                WS-INTEREST WS-CUM-INT
Parameter Picture Direction Meaning
1 9(7)V99 in loan balance
2 9V9(4) in annual interest rate, e.g. 0.0450 for 4.5%
3 9(7)V99 out this month's interest: balance × rate ÷ 12, rounded
4 9(9)V99 out total interest calculated by all calls so far

CALCINT must keep the running total in its own WORKING-STORAGE, not rely on the caller. For the input

03
IE10000100050000000450
IE10000200012000000600
IE10000300000999900399

the program prints

IE100001    5000.00      18.75
IE100002    1200.00       6.00
IE100003      99.99       0.33
TOTAL INTEREST        25.08

Don't change LOANINT itself.

fixed format
Run your program to see its output here. The first visible test's input and datasets are used.
Submit to grade your program against every test.