Cobol

To suggest any changes to this page please Contact Me.

The COBOL section was only begun recently. As a result some sections remain incomplete, and others haven't been begun. I apologize for any inconvenience.


Introduction

The Basics

Screen Output

Data Declaration

Screen Input

Assigning Values to Variables

Arithmetic Operations

Procedural Abstraction : Paragraphs

Procedural Abstraction : Sections

Control Structures : Selection

Control Structures : Iteration

Records

Sequential Files

Data Editing

Arrays

Indexed Files

Introduction

COBOL is about as old as your parents, so just think how old it is in computer years. It is still an important language though, mainly because there are thousands and thousands of applications out there written in COBOL, and someone has got to fix them and add to them. There aren't as many jobs for COBOL as for C++ or JAVA, but then again, not a lot of people are learning COBOL anymore.

COBOL really isn't that difficult either, especially if you know other structured languages like Pascal and C. You can easily learn enough to sound intelligent in a day. There aren't many data-structures (actually, everything is really a record), and there are no notions of scope or parameter passing.

The Basics

A "hello world" program is as good a way as any to understand the structure of a COBOL program, so here it is:

IDENTIFICATION DIVISION

PROGRAM-ID HELLO-WORLD

ENVIRONMENT DIVISION

DATA DIVISION

PROCEDURE DIVISION

FIRST-PARA.

    DISPLAY "Hello World".

STOP RUN

This is quite a bit more complex than a similar program an Pascal or C

The first thing to notice are the four divisions :

IDENTIFICATION DIVISION : This is where you must enter the program name. You can also enter when and by whom it was written.

ENVIRONMENT DIVISION : This is where you define the files the program needs. You also used to put hardware information here, but that isn't really necessary anymore. 

DATA DIVISION : This is where you declare variables, records, files etc. It is similar to the top of a Pascal program.

PROCEDURE DIVISION : This is where you write the program. In the sample above this is all in one paragraph. A paragraph is a series of statements named by a label (FIRST-PARA in this case). There is no specific mark to the end of a paragraph.

Each statement begins with a word like DISPLAY that describes the action to be performed by the statement.

There are a couple of hundred reserved words in COBOL so I find it useful to put hyphens in user defined names like HELLO-WORLD.

Screen Output

Screen output is always one of the first things you want to master with a new language. You have seen an example of screen output with the

  DISPLAY "Hello World".

statement. You can also use it to write digits:

 DISPLAY 300.

Or a combination:

 DISPLAY "The number is " 300.

The problem with number is that this technique only works for unsigned integers, a way around this is as follows:

 DISPLAY "-766.32".

To find out more about using floating-point and signed numbers, see the Arithmetic Operations section.

Also note that you can't make statements like :

 DISPLAY 2 + 3

You have to store the result of 2 + 3 in a variable first. See the Assigning Values to Variables section.

Data Declaration

The HELLO-WORLD program can be altered to demonstrate basic data declaration. In this program two string variables and one number variable are created and given initial values. These are then printed out.

IDENTIFICATION DIVISION

PROGRAM-ID HELLO-WORLD

ENVIRONMENT DIVISION

DATA DIVISION

WORKING-STORAGE SECTION.

01 WS_STRING-1    PIC X(20)

   VALUE "Hello World".

01 WS-STRING-2    PIC X(30)

   VALUE "This is program number ".

01 WS-PROGRAM-NUMBER    PIC 9(2)

   VALUE 1.

PROCEDURE DIVISION

FIRST-PARA.

   DISPLAY WS-STRING-1.

   DISPLAY WS-STRING-2.

   DISPLAY WS-PROGRAM-NUMBER.

STOP RUN

The WORKING-STORAGE SECTION is used for declaring variables other than files and associated records. The 01 before each variable is its level number - the need for these becomes apparent when we deal with records. This is followed by the data name, and then a PIC clause. The PIC clause establishes the type and size of the variable. 'X' represents a character, '9' represents a number. The number following in brackets tells the size (for X(4) you could also write XXXX).

Keyboard Input

Keyboard input is simple. We can use the variable WE-PROGRAM-NUMBER from above to store a new program number:

DISPLAY "Enter the program number".

ACCEPT WS-PROGRAM-NUMBER.

This can only be used to obtain a single variable at a time.

Assigning Values to Variables

The two main keywords used for this are MOVE and COMPUTE

Continuing with our variable WS-PROGRAM-NUMBER from the Data Declaration section, we can assign a new value for it with the following structure in the PROCEDURE DIVISION section :

MOVE TO  WS-PROGRAM-NUMBER.

We can give WS-STRING-1 a new value like this :

MOVE "Hello Everyone" TO WS-STRING-1.

There is a more advanced type of assignment that MOVE can't help us with. Given correctly declared variables, we can write the following in Pascal :

numA := numB * numC;

The value of numA becomes the value of the expression numB * numC. To do this in Pascal we need to use the COMPUTE  keyword.

Suppose we have created two number variables in the DATA DIVISION section called WS-NUMBER-1 and WS-NUMBER-2. These have been given values, and we want to multiply them together and store the result in WS-PROGRAM-NUMBER. We can do this with the following code in the PROCEDURE DIVISION section :

COMPUTE WS-PROGRAM-NUMBER = WS-NUMBER-1 * WS-NUMBER-2.

You could also just use numbers :

COMPUTE WS-PROGRAM-NUMBER = 10 * 5.

Remember to be careful when assigning values to variables. COBOL won't check to see if the new value is appropriate for the variable in either size or type. In fact, it is worth pointing out here, COBOL has very little built in error checking, so be careful.

Arithmetic Operations

The COMPUTE keyword above introduced arithmetic. Bare in mind that there is no mod operator available, and division is done with /.

With division, numbers can be rounded up like this :

COMPUTE WS-NUMBER-1 ROUNDED = 37/10.

COBOL can also handle floating point numbers. Floating point numbers are declared like this in DATA DIVISION :

WS-NUMBER-10    PIC 99V99.

This number can hold values with two leading and two trailing spaces like 23.98.

A confusing aspect of this is that to enter a number like this with ASSIGN the user would have to enter 2398.

COBOL can also deal with signed numbers. Simply put an S at the front :

WS-NUMBER-10    PIC S99V99.

We could now give it the value -32.76.

There are also a collection of keywords that deal with arithmetic. Given the right declarations statements like these sometimes occur :

ADD WS-NUMBER-1 TO WS-NUMBER-2.

MULTIPLY WS-NUMBER-1 BY WS-NUMBER-2 GIVING WS-NUMBER-3.

DIVIDE WS-NUMBER-1 INTO WS-NUMBER-2 GIVING WS-NUMBER-3 REMAINDER WS-NUMBER-4.

SUBTRACT WS-NUMBER-1 FROM WS-NUMBER-2 GIVING WS-NUMBER-3. 

This is all very silly, but COBOL is an old language. As you can see, the GIVING clause is optional.

There is an additional clause you may add to the end :

ADD WS-NUMBER-1 TO WS-NUMBER-2 

ON SIZE ERROR

    DISPLAY "Size error"

NOT SIZE ERROR

    DISPLAY WS-NUMBER-1

END-ADD

Procedural Abstraction

Just like Pascal and C, COBOL allows you to split your code up into small procedures. This is what the PROCEDURE DIVISION portion of a program will resemble when the PERFORM keyword is used to split the code into separate procedures. In this example the procedures (with names like PARA-ONE) call each other in turn :

CONTROL-PARA.

.....................

PERFORM PARA-TWO.

DISPLAY "Paragraph two has been executed".

STOP RUN.

PARA-ONE.

.....................

PARA-TWO.

.....................

PERFORM PARA-THREE.

DISPLAY "Paragraph three has been executed"

PARA-THREE.

DISPLAY "This is the last paragraph"

..................

Note that the paragraph names are arbitrary, and nothing explicitly marks the end of them. They can be called with PERFORM followed by their name. Also, unlike in Pascal, the code to be executed when the program begins is before the other procedures. Make sure the last statement in the first paragraph is STOP RUN or you will find yourself in a lot of trouble.

Sections

A whole group of paragraphs can be combined in a larger structure called a SECTION. A section can then be called with PERFORM, so in effect a section is like a sub-program.

SEC-ONE SECTION.

SEC-CONTROL.

................

PERFORM SEC-ONE-ONE.

..............

GO TO SEC-EXIT.

SEC-ONE-ONE.

.....................

SEC-EXIT.

EXIT.

This could then be called by :

PERFORM SECTION-ONE. Control is then passed to the first paragraph in SECTION-ONE, and doesn't return to the rest of the program until GO TO is executed. This is the equivalent of STOP RUN.

In case you were wondering, these aren't really procedures in the Pascal sense. You cannot pass them parameters, and they can't create their own independent variables - everything is global in COBOL.

Control Structures : Selection

COBOL has fewer structures for controlling program flow than other languages like C, but it does the job. 

Control Structures rely on conditionals to evaluate between alternatives. COBOL includes the following :

<, >, >=, <=, =, NOT=

These are self explanatory, and they also have word form equivalents:

LESS, GREATER, GREATER OR EQUAL, LESS OR EQUAL, EQUAL, NOT EQUAL

There are also the three main logical operators :

AND, NOT, OR

Selection statements can now be made as follows :

IF  WS-NUMBER-1 > 100

THEN

    DISPLAY "Greater than 100".

   ...........

END IF.

We can also add an ELSE clause :

IF  WS-NUMBER-1 > 100

THEN

    DISPLAY "Greater than 100". 

ELSE

    DISPLAY "Not greater than 100"   

END IF.

You can also nest selection structures inside other selection structures, just remember to put the right number of END IF statements in.

There is also the equivalent of the Pascal Case statement. This one evaluates WS-NUMBER-1, and calls a different procedure for each possible value :

ACCEPT WS-NUMBER-1.

EVALUATE WS-NUMBER-1

    WHEN "1" PERFORM PARA-ONE

    WHEN "2" PERFORM PARA-TWO

    WHEN "3" PERFORM PARA-THREE

    WHEN OTHER PERFORM PARA-FOUR

END-EVALUATE

You can also test to see if a piece of data is one of four class types :

ALPHABETIC, ALPHABETIC-UPPER, ALPHABETIC-LOWER, NUMERIC

So we can have statements like

IF WS-NUMBER-1 NUMERIC ......

Control Structures : Iteration

The Pascal While statement can be performed like this :

ACCEPT WS-NUMBER-1

PERFORM UNTIL WS-NUMBER-1 > 100

                        AND WS-NUMBER-1 < 1000

     DISPLAY "Still between 100 and 1000"

     ACCEPT WS-NUMBER-1

END-PERFORM

The Pascal For statement is implemented  like this :

 PERFORM TEST AFTER VARYING WS-INDEX FROM

        1 BY 1 UNTIL WS-INDEX = 10

    DISPLAY "WS-INDEX is now equal to " WS-INDEX

END-PERFORM

This is more complex than Pascal, but it is still pretty obvious what is going on - it starts at 1, increments by 1, and goes until 10.

Records

A record is a set of related information grouped together. They are extremely common in COBOL applications. Here is how one might be declared in COBOL to hold information about a person :

DATA DIVISION

WORKING STORAGE SECTION

01 WS-PERSON-RECORD

    03 WS-NAME        PIC X(20)

    03 WS-ADDRESS  PIC X(40)

    03 WS-AGE            PIC 9(2)

01 WS-STRING          PIC X(80)

Note that the top line has no PIC clause - this is the group item. The others are the elementary items

We can now use this is various ways. Firstly, we can initialize it as empty like this :

MOVE SPACES TO WS-PERSON-RECORD

We can now put some values into the elements :

MOVE 22 TO WS-AGE

MOVE "Dane" TO WS-NAME

MOVE "New Zealand" TO WS-ADDRESS

MOVE WS-ADDRESS TO WS-STRING

DISPLAY WS-STRING.

This is also possible :

MOVE WS-PERSON-RECORD TO WS-STRING

DISPLAY WS-STRING

The data in a record is alphanumeric. Even if it is a number, though, arithmetic arguments can't be applied to it.

You can also nest records inside records :

01 WS-PERSON-RECORD

    03 WS-NAME                PIC X(20)

    03 WS-ADDRESS

        05 WS-STREET        PIC X(20)

        05 WS-CITY            PIC X(20)

Note now that WS-ADDRESS doesn't have a PIC clause.

The reason the levels have been ordered 01, 03, 05 is simple. They could be ordered 01, 02, 03; but then if you wanted to insert a new level between 01 and 02, you would also have to change the value of 03.

Notice that you can directly reference a field, unlike in Pascal where you need to use A dot operator like  RecordName.FieldName. However, if a field name is used elsewhere for another purpose you need to give the record name :

MOVE SPACES TO WS-AGE IN WS-PERSON-RECORD

Sequential Files

A sequential file consists of a sequence of data items terminated by an end marker, and stored on a disk or some other permanent storage device.

To use a sequential file in COBOL you must make declarations in three of the four divisions.

Firstly, the following code is placed in the ENVIRONMENT DIVISION :

ENVIRONMENT DIVISION

INPUT-OUTPUT SECTION.

FILE-CONTROL.

    SELECT DATA-FILE-1

    ASSIGN TO DEVICE-NAME

    ORGANIZATION IS SEQUENTIAL

    ACCESS MODE IS SEQUENTIAL

Actually, the last two lines aren't needed since they are the default conditions for a sequential file.

Each file that is going to be used must have its own SELECT division.

With regards to DEVICE-NAME, this can vary, but usually DISK and PRINTER will be two of the options available.

Each file must also be defined in the DATA DIVISION section:

DATA DIVISION.

FILE SECTION.

FD DATA-FILE-1.

    01 PERSON-REC.

        03 NAME            PIC X(20)

        03 ADDRESS      PIC X(30)

These records are defined just the same as those in the WORKING-STORAGE section.

Now we can use READ and WRITE to work with these files in the PROCEDURE DIVISION section, but first it must be opened in this section. This code opens the file and transfers the first record to the record area defiled in FILE SECTION :

PROCEDURE DIVISON.

PARA-1.

    OPEN INPUT DATA-FILE-1.

    READ DATA-FILE-1

        AT END

            MOVE "E" TO WS-END-OF-DATA-FILE.

To open a file for writing use the structure :

OPEN OUTPUT DATA-FILE-1.

The AT END clause is necessary : it tells the file what to do when it hits the end of the file. In this example a status variable is assigned to WS-END-OF-DATA-FILE. Naturally, this needs to be declared and initialized earlier like this :

WORKING-STORAGE SECTION.

    01 WS-END-OF-DATA-FILE        PIC X

                        VALUE "N".

It is also useful to add a condition name like this on the next line :

        88 END-OF-DATA-FILE        VALUE "E"

We can now test for the end of the file in a loop :

PROCEDURE DIVISION

PARA-1.

    OPEN INPUT DATA-FILE-1.

    READ DATA-FILE-1.

        AT END

            MOVE "E" TO WS-END-OF-DATA-FILE

        NOT AT END

            ADD 1 TO RECORD-COUNT

    END READ

............

Given that RECORD-COUNT had been declared, it would count the number of records in the file.

We use the keyword WRITE to transfer data to a file. This code opens a file and writes a record :

PROCEDURE DIVISION.

PARA-1.

    OPEN OUTPUT DATA-FILE-1.

    MOVE "Dane" TO NAME

    MOVE "New Zealand" TO ADDRESS.

    WRITE PERSON-REC.

    CLOSE DATA-FILE-1.

............

Take into account though, that is DATA-FILE-1 already had data in it, a new file would be created, and the old data would be written over. Also note that unlike READ which accepted a file name as its parameter, WRITE accepts a record name. Also remember to CLOSE each file you open or bad things will happen.

WRITE also allows you to append records to an existing file. Simply declare the file as in the previous examples, and then open it with the following code :

    OPEN     EXTEND    DATA-FILE-1.

It is also possible to edit existing records in place by opening the file for input and output. This code searches the file for a record with a certain address, and then changes that address (note : RECORD-FOUND must first be declared in a similar fashion to WS-END-OF-DATA-FILE) :

PROCEDURE DIVISION.

PARA-1.

        OPEN  I-O DATA-FILE-1.

        READ DATA-FILE-1

            AT END

                MOVE "E" TO WS-END-OF-DATA-FILE

                DISPLAY "DATA NOT FOUND"

        END-READ

        PERFORM UNTIL END-OF-DATA-FILE OR RECORD-FOUND

            IF ADDRESS = "New Zealand"

                THEN

                    MOVE "Australia" TO ADDRESS

                    REWRITE PERSON-REC

                    MOVE "Y" TO WS-RECORD-FOUND

            ELSE

                READ DATA-FILE-1

                AT END

                    MOVE "E" TO WS-END-OF-DATA-FILE

                    DISPLAY "DATA NOT FOUND"

                END-READ

            END-IF

        END-PERFORM

        CLOSE DATA-FILE-1

        STOP RUN.

COBOL also allows you to create indexed files.

Data Editing

COBOL is mainly a language for creating data storage applications, so it has many built in facilities for data editing and report making. COBOL can handle these topics far better than other structured languages like Pascal and C.

A common situation in report writing is the need to write right aligned output like this

$200.50

$45,600.00

$20.00

$1,800,900.00

This would be very difficult in many languages, but COBOL can easily handle it. We simply create a data type like this :

    01 WS-CURRENCY-OUTPUT        PIC $$,$$$,$$9.99.

This is how the following numbers would be outputted :

2050            $20.50

3045000    $30,450.00

50             $0.50

As you can see, a number signaled by a "$" is only included if it's needed, and the output is right aligned.

Remember that these special data types are only used for output, you can't do arithmetic with them. 

You can also do the same thing with "+" and "-" signs :

PIC ++++++9.99

PIC ----9.9

This allows you to get rid of irrelevant zeros when you are outputting data.

Arrays        

Arrays aren't quite as natural to COBOL as they are to most other languages, but that is a sign of COBOL's age. Here is how an array might be created to store the salaries of ten employees :

01 WS-EMPLOYEE-REC

    03 WS-EMPLOYEE-SALARY    PIC 99999

        OCCURS 10 TIMES

You may think it would make sense to declare all this at level 01, but the OCCURS clause cannot be used at level 01.

You can then use parenthesis to refer to a particular element in the array :

DISPLAY  WS-EMPLOYEE-SALARY(3)

This will print out the fifth element (as opposed to C, where it would print out the sixth element).

We can create a variable like WS-INDEX, and use it in place of the number :

DISPLAY  WS-EMPLOYEE-SALARY(WS-INDEX)

But we can't use expressions like :

DISPLAY  WS-EMPLOYEE-SALARY(WS-INDEX + 1)

The value of WS-INDEX would need to be incremented before hand.

It is also simple to declare an array of records. This code creates 20 records with three fields each :

01 EMPLOYEE-REC-TABLE

    03 WS-EMPLOYEES    OCCURS 50 TIMES.

            05 WS-NAME        PIC X(20)

            05 WS-ADDRESS  PIC X(40)

            05 WS-SALARY    PIC 99999

We can now refer to an entire record at a time with :

WS-EMPLOYEE(10)

Or to a single field of a record :

WS-ADDRESS(30)

We can also create multi-dimensional arrays :

01 WS-TABLE VALUE ZEROS.

    03 WS-MONTH

            OCCURS 12 TIMES

        05 WS-FIVE-HIGHEST-TEMPERATURES    PIC 999

            OCCURS 5 TIMES

Note here the VALUE ZEROS. clause : this initializes all the elements to zero (including strings).

We can now access a specific element like this (which shows the 2nd highest temperature in May):

WS-TABLE(5, 2)

Or we could access all the temperatures for November like this :

WS-MONTH(11)

Indexed Files

Indexed Files have an advantage over sequential files in the sense that they can be accessed randomly. To find a record in a sequential file we need to look through all the records in order, but with an indexed file we can identify and retrieve records according to their unique key values.

This is how a file might be declared in the ENVIRONMENT DIVISION section :

ENVIRONMENT DIVISION.

INPUT-OUTPUT SECTION.

FILE-CONTROL.

        SELECT EMPLOYEE-FILE

        ASSIGN TO DISK

        ORGANIZATION IS INDEXED

        ACCESS MODE IS RANDOM

        RECORD KEY IS EMPLOYEE-NUMBER

        FILE STATUS IS EMPLOYEE-STATUS

The RECORD KEY is equivalent to a primary key in a relational database. Bare in mind that this cannot be changed once the record has been created.

The FILE STATUS clause is optional. It is must be declared as a two byte character in WORKING-STORAGE, and is used mainly for error handling.

The declaration in DATA DIVISION is the same as with sequential files, but remember to use the same key value as in ENVIRONMENT DIVISION :

DATA DIVISION.

FILE SECTION.

FD    EMPLOYEE-FILE.

    01 EMPLOYEE-REC.

        03 EMPLOYEE-NUMBER        PIC X(6).

        03 EMPLOYEE-NAME             PIC X(20).

        03 EMPLOYEE-SALARY          PIC 9(5).

WORKING-STORAGE SECTION.

    01 EMPLOYEE-STATUS               PIC XX.

We can now move onto the PROCEDURE DIVISION section.

Firstly we must open the file for input and output :

OPEN    I-O    EMPLOYEE-FILE.

Also, remember to close it when you have finished :

CLOSE EMPLOYEE-FILE.

To work with the file we use the keywords READ, WRITE, DELETE and REWRITE.

If we want to read a file, we give the key value of the record we want, and if it exists it will be transferred into the file's record area :

        MOVE "AR8763" TO EMPLOYEE-NUMBER.

        READ EMPLOYEE-FILE

            INVALID KEY

                DISPLAY "No such file exists."

This is how we write a record to file, supposing the variables mentioned had been created in the WORKING-STORAGE section :

        MOVE "RW3425" TO WS-EMPLOYEE-NUMBER

        MOVE "JERRY SEINFELD" TO WS-EMPLOYEE-NAME.

        MOVE 56000 TO WS-EMPLOYEE-SALARY

        WRITE EMPLOYEE-REC FROM WS-EMPLOYEE-REC.

We have first put the values into an intermediary storage area, and then written the record to file in one go.

To modify a record we would usually do a READ followed by a REWRITE  :

        MOVE "RW3425" TO WS-EMPLOYEE-NUMBER

        MOVE "KRAMER" TO WS-EMPLOYEE-NAME

        MOVE 65000 TO WS-EMPLOYEE-SALARY

        REWRITE EMPLOYEE-REC FROM WS-EMPLOYEE-REC

            INVALID KEY

                DISPLAY "Record doesn't exist."

This is how we could delete the record we just created (you would normally do a READ first to make sure you have the right record :

        MOVE "RW3425" TO EMPLOYEE-NUMBER

        DELETE EMPLOYEE-FILE

            INVALID KEY

                DISPLAY "That record doesn't exist."

    


 

Wednesday, February 23, 2000