22 April 2008

Fun with 88's: Part 2

My previous post introduced some of the syntax for defining local variables in COBOL. Now, we'll look at some procedural logic.

The PROCEDURE DIVISION consists of paragraphs of code, optionally organized into sections. Each paragraph consists of one or more statements and ends with a period (yes, a period: remember that the syntax was designed to resemble English). Paragraphs act like open subroutines in that control can fall into a paragraph and all program variables are accessible, so COBOL makes it easy to tangle your code into linguine—but you don't have to do it that way.

The EVALUATE statement is COBOL's switch, and the PERFORM statement executes a paragaph or a range of paragraphs. Common practice is to prefix paragraph names with a 4- or 5-digit number, which indicates where in the source code each is defined and how it fits into the execution hierarchy. As a result, programs tend to read top-down through the hierarchy rather than (my preference, which I adopted from Wirth) bottom-up.

A fragment of code for processing our name and address data from the previous post might be:



PROCEDURE DIVISION.
* * *
1200-LABEL-STATE.
EVALUATE CARD-STATE
WHEN "PA", "KY", "VA", "MA"
PERFORM 1210-LABEL-AS-COMMONWEALTH
WHEN "DC"
PERFORM 1220-LABEL-AS-DISTRICT
WHEN OTHER
PERFORM 1290-LABEL-AS-STATE
END-EVALUATE.

1210-LABEL-AS-COMMONWEALTH.
* * *
1220-LABEL-AS-DISTRICT.
* * *
1290-LABEL-AS-STATE.
* * *



Fairly readable, maintainable code, but a little brittle, should we need this classification scheme somewhere else in the program. Named conditions will help us out here.

The special level number 88 identifies a value or values that an elementary item might hold and a name for the condition that indicates that the item currently holds the value. Turning back to our example variable definitions:



* * *
01 CARD-IMAGE.
03 CARD-NAME-AND-ADDRESS.
05 CARD-NAME.
07 CARD-FIRST-NAME PIC X(10).
07 CARD-LAST-NAME PIC X(10).
05 FILLER PIC X(3).
05 CARD-ADDRESS.
07 CARD-ADDRESS-LINE-1 PIC X(15).
07 CARD-ADDRESS-LINE-2 PIC X(15).
07 CARD-CITY PIC X(10).
07 CARD-STATE PIC X(2).
88 CARD-IS-DISTRICT VALUE "DC".
88 CARD-IS-COMMONWEALTH VALUE "PA", "KY", "VA", "MA".
07 FILLER PIC X(8).
07 CARD-ZIP-CODE PIC 9(5).
03 FILLER PIC X(2).



Then our procedural code simplifies to:



PROCEDURE DIVISION.
* * *
1200-LABEL-STATE.
EVALUATE TRUE
WHEN CARD-IS-COMMONWEALTH
PERFORM 1210-LABEL-AS-COMMONWEALTH
WHEN CARD-IS-DISTRICT
PERFORM 1220-LABEL-AS-DISTRICT
WHEN OTHER
PERFORM 1290-LABEL-AS-STATE
END-EVALUATE.

1210-LABEL-AS-COMMONWEALTH.
* * *
1220-LABEL-AS-DISTRICT.
* * *
1290-LABEL-AS-STATE.
* * *



Now, if we find that requirements change, for instance that data for Mexico has to be supported, we have only one place that has to be updated to accommodate "DF" for the Distrito Federal:



07 CARD-STATE PIC X(2).
88 CARD-IS-DISTRICT VALUE "DC", "DF".
88 CARD-IS-COMMONWEALTH VALUE "PA", "KY", "VA", "MA".



(We'd have to make some other changes, too, but that's not my point here.)

88-level conditions can use sets of values that overlap, and ranges can be specified with the keyword THRU. Returning to the original example:



07 CARD-STATE PIC X(2).
88 CARD-IS-DISTRICT VALUE "DC".
88 CARD-IS-COMMONWEALTH VALUE "PA", "KY", "VA", "MA".
88 CARD-IS-13-ORIGINAL VALUE "MA", "NH", "RI", "CT", "NY",
"NJ", "PA", "DE", "MD", "VA", "NC",
"SC", "GA".



THRU is generally more useful with numeric data. Consider this contrived example of a tax calculation. The S in the PICTURE indicates the sign, and the V an implicit decimal point.



07 TAXABLE-INCOME PIC S9(6)V9(2).
88 BRACKET-IS-10-PCT VALUE 0 THRU 10000.
88 BRACKET-IS-15-PCT VALUE 10000.01 THRU 20000.
* * *



Aside: the "." symbol in the previous line is used in two very different ways: as a terminator period, as we've seen before, and as a decimal point in the numeric literal 10000.01. Getting these two right is one of the masochistic joys of machine-translating COBOL.

Next: to REDEFINE the union.

No comments: