LevSelector.com |
Cobol - incomplete tutorial | |
• Introduction
• The Basics • Screen Output • Data Declaration • Keyboard 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 | home - top of the page - |
This tutorial is adapted from: http://homepages.paradise.net.nz/milhous/cobol.htm
COBOL is an old language, it is used on mainframes. There was quite a demand for COBOL programmers before year 2000 - to fix old programs. Today (2001) it is still an important language, but 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 | home - top of the page - |
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 |
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 it is useful
to put hyphens in user defined names like HELLO-WORLD.
Screen output | home - top of the page - |
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 | home - top of the page - |
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 | home - top of the page - |
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. |
Assigning Values to Variables | home - top of the page - |
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 5
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 COBOL 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 | home - top of the page - |
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 | home - top of the page - |
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.
PARA-THREE.
|
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 | home - top of the page - |
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. |
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 | home - top of the page - |
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 | home - top of the page - |
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 | home - top of the page - |
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 | home - top of the page - |
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. |
Data Editing | home - top of the page - |
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
Arrays | home - top of the page - |
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 | home - top of the page - |
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. |
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." |