Common COBOL verbs / Statements - II

=============
# Arithmetic
=============

To perform arithmetic calculations there are two ways of going about doing this:

using the ADD, SUBTRACT, MULITPLY, DIVIDE verbs, or using the COMPUTE
verb as seen already. The formats for the first group are as follows:
[square brackets indicate optional words]


ADD {identifier-1 or literal}... TO {identifier-2 or literal}...
[GIVING {identifier-3}]
[NOT] [ON SIZE ERROR {statements}]
[END-ADD]

Examples:
ADD NUM-A TO NUM-B GIVING NUM-TOTAL-1
ADD NUM-A, 20 TO NUM-B GIVING NUM-TOTAL-2
ADD 3 TO NUM-TOTAL-3

When the word GIVING is not used (as in the third example) the
identifier that follows 'TO' is where the result of the addition. This
also applies to SUBTRACT and MULTIPLY. ON SIZE ERROR is a conditional,
whereby if the result of the calculation is larger than the PIC
description (i.e. the result is truncated either at the leading end or
the decimal places). On such an occasion a series of statements can be
executed. The use of ON SIZE ERROR means that a scope terminator is
required (END-ADD). The second example adds both NUM-A and 20 to
NUM-B.


SUBTRACT {identifier-1 or literal}... FROM {identifier-2 or literal}...
[GIVING {identifier-3}]
[NOT] [ON SIZE ERROR {statements}]
[END-SUBTRACT]

Examples:
SUBTRACT 200 FROM NUM-C GIVING NUM-D
ON SIZE ERROR DISPLAY 'NUM-D is out of range'
END-SUBTRACT
SUBTRACT NUM-F FROM 20 ** this won't work! **

The second example is illegal because, in the absence of a receiving
identifier after GIVING, the result of the subtraction has nowhere to
go (20 is a literal). The same would apply to ADD and MULTIPLY.


MULTIPLY {identifier-1 or literal}... BY {identifier-2 or literal}...
[GIVING {identifier-3}][ROUNDED]
[NOT] [ON SIZE ERROR {statements}]
[END-MULTIPLY]

Examples:
MULTIPLY NUM-G BY 20 GIVING NUM-F
MULTIPLY 20 BY NUM-G

DIVIDE {identifier-1 or literal} BY {identifier-2 or literal}...
GIVING {identifier-3} [ROUNDED] [REMAINDER {identifier-4}]
[NOT] [ON SIZE ERROR {statements}]
[END-DIVIDE]

Examples:
DIVIDE NUM-H BY 3 GIVING NUM-I REMAINDER NUM-REMAIN
DIVIDE NUM-Y BY 3 GIVING NUM-K ROUNDED

The DIVIDE statement differs from the previous 3 in that GIVING is
required. Also, the remainder of the division (e.g. 7 divided by 3
equals 3 remainder 1) can be stored in an identifier. The ROUNDED
option, which is also available for the MULTIPLY statement, will round
to the nearest significant decimal place, defined by the PIC clause.
E.g.:


000100 77 NUM-A PIC 99 VALUE 10.
000200 77 NUM-B PIC 9V99.
002000 DIVIDE NUM-A BY 3 GIVING NUM-B
002010 ON SIZE-ERROR DISPLAY 'RESULT IS TRUNCATED'
002020 END-DIVIDE
002030
002040 DIVIDE NUM-A BY 3 GIVING NUM-B ROUNDED
002050 ON SIZE-ERROR DISPLAY 'RESULT IS TRUNCATED'
002020 END-DIVIDE

The first DIVIDE statement will result in a size error (20 / 3 =
6.66666..) as NUM-B will contain 6.66 but will have truncated the
rest. This does not apply to the second DIVIDE statement since it has
been rounded to fit the pic description 9V99, and so in this case
NUM-B will contain 6.67.


DIVIDE {identifier-1 or literal} INTO {identifier-2 or literal}...
GIVING {identifier-3} [ROUNDED] [REMAINDER {identifier-4}]
[NOT] [ON SIZE ERROR {statements}]
[END-DIVIDE]

Examples:
DIVIDE 3 INTO NUM-Y GIVING NUM-K ROUNDED
This differs from the previous DIVIDE statement only in the order of
numerator and denominator (both mean NUM-Y / 3).


COMPUTE

As previously seen in earlier sections, COMPUTE can be used to do
arithmetic calculations. The format is:


COMPUTE {identifier-1} [ROUNDED] = arithmetic expression
[NOT] [ON SIZE ERROR {statements}] [END-COMPUTE]


with the operations:


+ add
- subtract
* multiply
/ divide
** to the power of

Note that brackets need to be used for complex calculations where
signs have presidence over each other, for example: 2 + 3 * 2 equals 8
(and not 10) since 3 * 2 is calculated before the addition
-----------------------------------------------------------------------------------------

==========
# Strings
==========

STRING {identifier-1 or literal-1} DELIMITED BY {identifier-2 or
literal-2 or SIZE}...
INTO {identifier-3}
ON OVERFLOW [statements]
&NOT ON OVERFLOW [statements]
END-STRING


STRING will move a series of strings into a destination string (from
left to right without space filling). If the destination string is not
large enough to hold all the source strings then this can be detected
and acted on by the ON OVERFLOW condition. The DELIMITED word
specifies the source string characters to be used:


01 W-DAY PIC XXX VALUE 'MON'.

01 W-MONTH PIC XXX VALUE '5 '.

01 W-YEAR PIC XXXX VALUE '2000;'.

: STRING W-DAY DELIMITED BY SIZE

'/' DELIMITED BY SIZE

W-MONTH DELIMITED BY SPACES

'/' DELIMITED BY SIZE

W-YEAR DELIMITED BY ';'

INTO DATE-STRING

END-STRING

The item DATE-STRING will contain "MON/5/2000".
===============
UNSTRING
===============

UNSTRING {identifier-1 or literal-1} DELIMITED BY {identifier-2 or
literal-2 or SIZE}...
INTO {identifier-3 COUNT IN identifier-4}...
TALLYING IN {identifier-5}
ON OVERFLOW [statements]
NOT ON OVERFLOW [statements]
END-UNSTRING

UNSTRING allows you to break up a string into small strings placed
into new items:

01 W-LONG-STRING PIC X(50) VALUE 'Name;Address;Post Code'.

UNSTRING W-LONG-STRING DELIMITED BY ';'

INTO W-NAME COUNT IN CHARS-NAME

W-ADDRESS COUNT IN CHARS-ADDR

W-POST-CODE COUNT IN CHARS-PCODE

TALLYING IN NUM-STRINGS-OUT

END-UNSTRING


Here then string 'Name' will be placed into W-NAME, containing 4
characters, thus CHARS-NAME will contain the value of 4. Likewise for
W-ADDRESS ('Address') CHARS-ADDR (7) etc... Notice how the ; character
has been lost. Any character, including spaces can be used as a
delimiter. TALLYING IN will count the number of items that were filled
by the UNSTRING operation, in this case NUM-STRINGS-OUT will contain
the value 3. Lastly, the ON OVERFLOW detects when each target of the
UNSTRING operation has been used but there remains unused characters
in the source string, e.g. if W-LONG-STRING contained
'Name;Address;Post Code;Country'.

=============
INSPECT
=============

INSPECT {identifier-1} REPLACING CHARACTERS BY {identifier-2 or literal-1}
{BEFORE or AFTER} [INITIAL {identifier-3 or literal-2}]
{ALL or LEADING or FIRST} {identifier-4 or literal-3}
BY {identifier-5 or literal-4} {BEFORE or AFTER} INITIAL
{identifier-6 or literal-5}


This form of INSPECT allows you to change characters within a string
using the various options above.

INSPECT {identifier-1} TALLYING {identifier-2}
{BEFORE or AFTER} [INITIAL {identifier-3 or literal-2}]
{ALL or LEADING or FIRST} {identifier-4 or literal-3}
BY {identifier-5 or literal-4} {BEFORE or AFTER} INITIAL
{identifier-6 or literal-5}


Here the source string is inspected and a tally of the number of
characters defined (using the subsequent options) is held in
{identifier-2}.

------------------------------------------------------------------------------------------------------
=========
# Write
=========


To output data to the printer or to a file, the verb WRITE is used. It
would be of the form:

WRITE {level 01 name of file/printer FD}

For example:

000100 ENVIRONMENT DIVISION.
000200 INPUT-OUTPUT SECTION.
000300 FILE-CONTROL.
000400 ASSIGN PRINT-FILE TO PRINTER.
000500 DATA DIVISION.
000600 FILE SECTION.
000700 FD PRINT-FILE.
000800 01 P-DATA PIC X(80).
: 000900 WORKING-STORAGE SECTION.
001000 01 DATA-NUMBER PIC 9(6) VALUE 123456.
001100 01 PRINT-NUMBER PIC X(6).
: 010900*in procedure division
011100 MOVE DATA-NUMBER TO PRINT-NUMBER
011200 MOVE PRINT-NUMBER TO P-DATA
011300 WRITE P-DATA

To simplify things the word FROM can be used to save always having to
first MOVE the data (PRINT-NUMBER) into the printing item (P-DATA
above). So, line 011200 and 011300 can simply be written as:

011100 WRITE P-DATA FROM PRINT-NUMBER


In addition to WRITE, the is also REWRITE and DELETE which are used to
update records within files that have been opened in I-O mode (see the
following section). When using DELETE you must first read the record
that is to be deleted. Also, when deleting a record you refer to the
FILE NAME rather than the record name:

000300 FD IN-FILE.
000400 01 CUST-RECORD.
000500 03 C-NAME PIC X(20).
000600 03 C-NUMBER PIC 9(6).
001000* in procedure division.
001100 READ IN-FILE.
001200 NOT AT END.
001300 IF C-NUMBER = 123456 THEN
001400 DELETE IN-FILE
001500 ELSE MOVE C-NUMBER TO W-DATA-STORE
001600 END-IF
001700 END-READ
--------------------------------------------------------------------------------------------------------
==================
# Scope Terminators
==================


In the section COBOL basics I mentioned the full stop (period). This
is what can be described as a scope terminator. Many COBOL verbs have
their own scope terminator, for example, END-IF, END-READ, END-PERFORM
etc... The purpose of a scope terminator is to define when a verb's
scope (i.e. associated logic) is finished.


For example:

READ IN-FILE
AT END MOVE 'Y' TO EOF FLAG
NOT AT END
IF REC-IN = 'Z' THEN
PERFORM PROCEDURE-X
END-IF
END-READ


In the above example END-READ defines the scope of the READ statement
since there is a condition involved (AT END of the file, or NOT AT END
of the file), while END-IF defines then end of the IF condition, i.e.
END-READ and END-IF define their scope. Any code that follows the read
statement will apply regardless of the READ condition (which is what
you would want in the above example). Without END-READ the subsequent
code would only be performed while NOT AT END is true: some nasty bugs
could ensue! Things become even more scary if you forget to use END-IF
or END-PERFORM (especially when looping). There's a good chance the
compiler might pick up the error.

However, a period is also a scope terminator. You might also code:


READ IN-FILE
AT END MOVE 'Y' TO EOF FLAG
NOT AT END
PERFORM UNTIL REC-IN = 'A'
IF REC-IN = 'Z' THEN
PERFORM PROCEDURE-X.
END-PERFORM

This would have the same effect as the first example (assuming the
compiler doesn't complain). Some people do use periods in place of
END-IF etc (note: I'm not sure you allowed to replace END-PERFORM
however). Problems may arise when you forget to use a scope terminator
somewhere and there's a period somewhere further down the code then
the compiler might just get confused.

It is important to realise that the period will terminate all ongoing
conditions. So in the above example, the period will act as both an
END-IF, END-PERFORM and END-READ.

Look at this paragraph:


000090*this works, using period scope terminators
000100 PARAGRAPH-ABC.
000200 MOVE 0 TO N
000300 PERFORM UNTIL N > 10
000400 COMPUTE N = N + 1
000500 DISPLAY N.
000600
000700 PERFORM PROCEDURE-Y N TIMES
000800 PERFORM UNTIL END-OF-FILE
000900 READ IN-FILE
001000 AT END MOVE 'Y' TO EOF-FLAG
001100 NOT AT END
001200 ADD VALUE-FROM-RECORD TO N GIVING X.
001300 001400 END-PERFORM
001500 DISPLAY X.


In the first example, the code will display numbers 1 to 10. It will
then perform PROCEDURE-Y 11 times. Finally, the numbers coming from
the IN-FILE (VALUE-FROM-RECORD) will be added to 11 giving X, which is
then displayed.

But what if we were to forget to put a period at the end of line 500?

000090*this has a syntax error
000100 PARAGRAPH-ABC.
000200 MOVE 0 TO N.
000300 PERFORM UNTIL N > 10
000400 COMPUTE N = N + 1
000500 DISPLAY N
000600 000700 PERFORM PROCEDURE-Y N TIMES.
000800 PERFORM UNTIL END-OF-FILE
000900 READ IN-FILE
001000 AT END MOVE 'Y' TO EOF-FLAG
001100 NOT AT END
001200 ADD VALUE-FROM-RECORD TO N GIVING X.
001300 001400 END-PERFORM
001500 DISPLAY X.


Now, the period on line 700 will terminate the scope of the PERFORM
statement on line 300. This means that PROCEDURE-Y gets performed
1+2+3+4+5+6+7+8+9+10+11 times (that's 66 times!). Oh dear.

In fact, when I tried to test these code fragments by compiling [on
the Fujitsu COBOL v3] it complained bitterly! The compiler was
particularly bothered by the lack of END-PERFORMS.

I was taught to only use 2 periods in any paragraph: the first after
the paragraph name, the second (and last) at the end of the paragraph.
So always use the verb's own scope terminator. More typing but less
headaches in my humble opinion. Here's what the above code would look
like when following this advice:


000090*using just 2 periods
000100 PARAGRAPH-ABC.
000200 MOVE 0 TO N
000300 PERFORM UNTIL N > 10
000400 COMPUTE N = N + 1
000500 DISPLAY N
000600 END-PERFORM
000700 PERFORM PROCEDURE-Y N TIMES
000800 PERFORM UNTIL END-OF-FILE
000900 READ IN-FILE
001000 AT END MOVE 'Y' TO EOF-FLAG
001100 NOT AT END
001200 ADD VALUE-FROM-RECORD TO N GIVING X
001300 END-READ
001400 END-PERFORM
001500 DISPLAY X.


Many of the commands described in this section have already been used
in earlier sections but here their description will be shown alongside
related commands, clauses and verbs. It should be noted that a command
probably is a verb, while a clause is a collection of COBOL words
without a verb.

No comments:

Post a Comment