Shawn M. Gordon
I’ve been coding COBOL since 1983, for some people that’s a long time, for others that makes me a new pup in the pound. During that time, probably 90% of my COBOL coding was done on an HP 3000 mini-computer system. HP had a pretty slim implimentation of the COBOL standard, mising things like the SCREENS SECTION, and being a bit late with the ’85 standard, but they also put in one very neat feature called Macros, I mention this because it will apply later. I’ve got a bit of history and pre-amble I’m going to go through, stick with me though because it all comes together and there is some good stuff here. I’m going to show a variety of interesting and relevant examples for the current “real world”.
I was very active in the HP 3000 community as a developer and an author. I put out about 200 columns in print magazines over the years and about half of those were a “tips” column for the COBOL language. In 1999 I left the HP 3000 world and the comfort of a 9 to 5 job to start a new Linux software company whose initial mission statement was to essentially create a RAD/IDE Visual Basic type environment that used COBOL for syntax throughout as opposed to the variety of hybrids that were available at the time. My plan was a native compiler, not an interpreter and to just work on Linux.
In early 2002 we released KOBOL, but with a few changes from our original plan. We did come up with a native compiler, but we did it in a unique way, we generate GCC code and then compile from there. The huge advantage of this is that GCC runs on just about every operating system and piece of hardware out there, it’s maintained by a large group of very skilled developers, generates native executables on each targe platform and is very robust. In addition we made Kobol work on Linux and Windows (a Mac OS X version is also very likely). At this stage we just released it with the IDE and not the RAD portion, but this gives you project management, integrated CVS, syntax highlighting and optional debugging.
Unlike any of the other COBOL vendors, we priced ours for the common man, the base application is less than $75 per developer and no run time costs, all platforms are supplied for the one price. We have a plug in system that lets you get just the pieces you want, like our HP 3000 compatibility module, which among other things gives you the Macro capability that I mentioned earlier, and which I’m going to use in some examples here in a minute. We’ll be releasing a SQL pre-process module late in the summer of 2003 that will provide a standardized and standards compliant interface to a variety of databases and technologies such as MySQL, PostgreSQL, DB2, Oracle and ODBC.
So last year the ANSI committee after many many years of deliberation finalized the 2003 standard for COBOL that includes the Object Oriented extensions. We’ve got part of the OO stuff finished in Kobol, and we’ll be using it to properly do the RAD portion of our product, but we’re talking “real world” here, so let’s look at what people are asking us about for Kobol features, how OO might impact you and how you can simulate OO capability. For good measure we’re going to look at the SQL pre-processor because a lot of people aren’t even aware it exists
or how to use it.
The main thing we get asked about is how to make a GUI application with Kobol or if we can make one. We don’t currently have the ability to create GUI applications, that is what we are working on currently and we will use the Qt toolkit, which will provide you with the same multi-platform capability as our compiler. Currently I know of two add-on systems that you can use with a variety of COBOL compilers, they are sp2 from Flexus and GUI ScreenIO from Norcom. I tried using Fujitsu for this a number of years ago, but could never quite get it to do what it was I needed to do, I’m sure it’s better now though.
COBOL business logic pretty much rules the world, there is really no other language better suited to business than COBOL (although I have to admit that I’m liking Python more and more). All of us old guys who wrote that code understand the business processes better than anyone else too. So why go to a new language to put a “pretty” front end on things if you don’t need to? There is an interesting theory about the perceived death of COBOL and “growth” of languages like C and Java. It is simply because things like C and Java are so convoluted that they require lots of books and magazines and web sites dedicated to explaining and supporting them, so they have a high perceived use, whereas COBOL “just runs” and doesn’t really need that kind of infrastructure associated with it. Your business logic is likely solid, you just spent a lot of time and money on Y2K upgrades, there’s no reason to throw out that code to chase some phantom.
With all that said, the World Wide Web is certainly a firm reality, and there are advantages to being about to write server side applications in COBOL, commonly called CGI. The idea is simple, but it can also be a bit confusing.
CGI is the common abbreviation for applications that run in web servers to feed data back to a web page, they can also accept data from the web page using the GET or POST method, we are going to describe the GET method here. You can look at Figure 1 to get an idea of what some of the HTML might look like that will generate the following URL (this is pretty common stuff on web pages).
What we have to do is retrieve the contents of the system variable “QUERY_STRING” that will contain the name/data pairs that get submitted from the web page form. The URL will look something like:
Where the relevant part that gets posted to QUERY_STRING is
‘name’ is the name of the variable and ‘value’ is the actual contents of the variable. The ‘&’ is a delimiter between each name/value pair.
Figure 2 shows a short example that will get the contents of QUERY_STRING and then parse the results into a table. This is obviously a fairly simplistic example, but it provides you with all the information you need to get data from a form. You can extend this example so that you are getting database key values, reading them from whatever you are using as your data source and generate HTML back out, writing HTML output is just a matter of coding in the bits you want, not really any different than writing any other kind of a report. All of this can be a lot easier using the Macros that I mentioned earlier, so I’m going to take the opportunity to show you how really cool this feature is that HP came up with.
Now we’ve all gotten creative over the years with copylibs and $include files and sub-programs for making as re-usable code as possible, and that is the big point with Object Orientation is that the code is reusable and mutable, you can inherit from some already defined object and customize it for a particular function if you want. The reality is that no one is going to go back and re-work their COBOL code to use it (or very few), but there are cute things you can do to incrementally add things like object orientation to your code, and one of those other tools in the toolbox is Macros.
A Macro in Kobol is more powerful than a what you’ll see in C, if you use that language. It consists of a keyword that identifies the Macro, followed by the code associated with the Macro. This code can accept replacement parameters if you want to make it even more versatile. An important consideration with Macros is that the code in the Macro is resolved at compile time. This means that code is actually copied into the location where the reference to the Macro is. This is good because it keeps your code from branching around unnecessarily and inlines everything while still keeping it readable. Take a look at Figure 3 for some example code.
Macros can be embedded in each other by changing the delimeter and keychar associated with it, which you can see in Figure 4. An interesting thing about macros is that they can be used anywhere in the program, including varable naming or specification, it literally replaces the code while it is being pre-processed, you can get exceptionally creative with this, I’ve seen some really extreme examples.
Before leaving this topic, I’m going to actually give you a useful example. Sorting can always be a bit of a pain depending on the operating system you are on or what your compiler vendor supports. I’ve used bubble sorts when I’ve just had a small table I needed to sort, it’s only a few lines of code and is quick to execute. Now a Shell sort is more complex, but also much more effecient, what I have in Figure 5 is a macro that will let you do a Shell sort of a table. Speaking of sorts, if I remember correctly the 2003 standard now has a verb for sorting tables in memory, I imagine this will be implimented differently by different vendors, but that’s going to be a very nice addition.
Finally I want to talk about the SQL pre-processor. As you are likely aware, the SQL language is a standard method of interfacing with a database. There is a common misconception that a relational database is required for SQL, but this isn’t the case, I’ve seen SQL interfaces put on top of Network database, xBase files (b-tree types, vsam, isam, etc.), Heirarchical databases, and object oriented databases. The advantage to using this in your code is that you can change database backends with virtually no change to your code, especially if you’ve externalized the connection information.
Let me give you an example of how you might want to use something like this. Say you are porting from the HP 3000 and its MPE operating system and Image database, you want to keep your code, just change the database calls. To prototype this (at least with Kobol which supports MySQL, PostgreSQL, DB2, Oracle and ODBC in its upcoming module) you could create a small MySQL database, port some data in to it, write your code and test the logic, etc. You’ve now managed to prototype and test while spending less than $100 in products. You could now connect to any of the other listed databases and not have to change your code (unless of course you are making use of features unique to a particular database).
Figure 6 is some old training code that HP had available when they first came out with their Allbase relational database. It’s a better training example than anything I have available, and I think it does a decent job of getting the point across of how it works. Basically you put your SQL blocks inside of:
I’ve trimmed out a lot of other stuff from the example, I mostly want you to get a sense of how it is structured. Essentially this example retrieves and displays single rows from a table. You can see how making use of this and some other things we talked about, you can make some pretty powerful, portable code that could be used in any modern situation without having to drammatically alter your code potentially.
We all get frustrated hearing about the demise of COBOL and looking around us at the massive installed base of said language. People use words like “glamorous” or “sexy” referring to some of the latest languages, but really it comes down to using the right tool for the job. I’m not going to use a hammer to change spark plugs, I’m not going to write an operating system in COBOL and I’m not going to write an accounting system in C or Java.
* theKompany.com (www.thekompany.com)
* Kobol (www.thekompany.com/products/kobol)
* Eclipse (www.eclipse.org)
* Qt (www.trolltech.com)
* sp2 (www.flexus.com)
* GUI ScreenIO (www.screenio.com)
* Python (www.python.org)
* SQL examples (http://docs.hp.com/mpeix/onlinedocs/36216-90081/36216-90081.html)
Figure 1 input type="text" maxlength="10" name="field1" size="10" input type="radio" checked="" maxlength="2" name="field2" size="2" value="value2" input type="password" maxlength="10" name="field3" size="10" Figure 2 WORKING-STORAGE SECTION. 01 M1 PIC s9(4) COMP VALUE 0. 01 M2 PIC S9(4) COMP VALUE 0. 01 INPUT-BUFFER PIC X(80) VALUE SPACES. 01 INPUT-TABLE. 03 IPT PIC X(30) OCCURS 10. 01 NAME-TABLE. 03 NT-ENTRY PIC X(10) OCCURS 20. 01 VALUE-TABLE. 03 VT-ENTRY PIC X(10) OCCURS 20. PROCEDURE DIVISION. * indicate what environment variable you want to access display "QUERY_STRING" upon environment-name. * get the value for the indicated environment variable accept INPUT-BUFFER from environment-value. MOVE SPACES TO NAME-TABLE VALUE-TABLE INPUT-TABLE. * assume INPUT-BUFFER looks like * name1=value1&name2=value2&name3=value3&name4=value4 INSPECT INPUT-BUFFER TALLYING M1 FOR ALL '&'. UNSTRING INPUT-BUFFER DELIMITED BY "&" INTO IPT(1) IPT(2) IPT(3) IPT(4). PERFORM VARYING M2 FROM 1 BY 1 UNTIL M2 >= M1 UNSTRING IPT(M2) DELIMITED BY '=' INTO NT-ENTRY(M2) VT-ENTRY(M2) DISPLAY 'Name Token : ' NT-ENTRY(M2) DISPLAY 'Value Token: ' VT-ENTRY(M2) END-PERFORM. Figure 3: 01 MODE1 PIC S9(4) COMP VALUE 1. 01 MODE5 PIC S9(4) COMP VALUE 5. 01 MODE7 PIC S9(4) COMP VALUE 7. 01 DB-STATUS-AREA. 03 DB-CONDITION-WORD PIC S9(4) COMP VALUE 0. 88 DB-CALL-OK VALUE 0. 88 DB-END-OF-CHAIN VALUE +15. 88 DB-RECORD-NOT-FOUND VALUE +17. 03 DB-DATA-LENGTH PIC S9(4) COMP VALUE 0. 03 DB-RECORD-NUMBER PIC S9(4) COMP VALUE 0. 03 DB-CHAIN-LENGTH PIC S9(4) COMP VALUE 0. 03 DB-BACK-ADDR PIC S9(4) COMP VALUE 0. 03 DB-FORWARD-ADDR PIC S9(4) COMP VALUE 0. 01 DS-CUST-MAST PIC X(26) VALUE "CUST-MAST;". 01 DB-BASE-NAME PIC X(28) VALUE " MYDB.PUB;". 01 DB-PASS-WORD PIC X(08) VALUE "READ;". 01 DB-LIST-ALL PIC X(02) VALUE "@;". 01 DB-SAME-LIST PIC X(02) VALUE "*;". 01 DB-ARGUMENT PIC X(06) VALUE SPACES. 01 DI-CUST-NUM PIC X(16) VALUE "CUST-NUM;". 01 CUST-MAST PIC X(256) VALUE SPACES. PROCEDURE DIVISION. A0000-MACROS. * !1 = variable with data base name * !2 = db open mode. $DEFINE %DBOPEN = CALL "DBOPEN" USING !1, DB-PASS-WORD, !2, DB-STATUS-AREA# * * !1 = data base variable * !2 = data set variable * !3 = data set search item * !4 = search item argument $DEFINE %DBFIND= CALL "DBFIND" USING !1, !2, MODE1, DB-STATUS-AREA, !3, !4# * * !1 = data base variable * !2 = data set variable * !3 = get mode * !4 = buffer to hold record returned * !5 = search item argument $DEFINE %DBGET= CALL "DBGET" USING !1, !2, !3, DB-STATUS-AREA, DB-LIST-ALL, !4, !5# * * !1 = data base variable * !2 = data set variable * !3 = close mode $DEFINE %DBCLOSE= CALL "DBCLOSE" USING !1, !2, !3, DB-STATUS-AREA# * $DEFINE %DBEXPLAIN= CALL "DBEXPLAIN" USING DB-STATUS-AREA# * A1000-INIT . %DBOPEN(DB-BASE-NAME#,MODE1#). IF NOT DB-CALL-OK %DBEXPLAIN STOP RUN. A1100-LOOP. MOVE "123456" TO DB-ARGUMENT. %DBFIND(DB-BASE-NAME#, DS-CUST-MAST#, DI-CUST-NUM#,DB-ARGUMENT#). IF DB-CALL-OK PERFORM UNTIL DB-END-OF-CHAIN %DBGET(DB-BASE-NAME#, DS-CUST-MAST#, MODE5#,CUST-MAST#,DB-ARGUMENT#) END-PERFORM ELSE %DBEXPLAIN GO TO C9000-EOJ. C9000-EOJ. %DBCLOSE(DB-BASE-NAME#,DS-CUST-MAST#,MODE1#). STOP RUN. Figure 4: $DEFINE %DBCHECK= IF (DB-CONDITION-CODE <> 17) AND (DB-CONDITION-CODE <> 0) CALL "DBEXPLAIN" USING DB-STATUS-AREA CALL INTRINSIC "QUIT" USING \!1\ END-IF# $PREPROCESSOR KEYCHAR=\,DELIMITER=~ $DEFINE \DBGET= CALL "DBGET" USING !1, !2, !3, DB-STATUS-AREA, DB-LIST-ALL, !4, !5 %DBCHECK(!6)~ $PREPROCESSOR KEYCHAR=%,DELIMITER=# Figure 5: * SHELL SORT ROUTINE * * This macro expects parameter 1 to be the element of the * table to be sorted. This sort compares the entire element. * Parameter 2 is the element hold area. Can be a higher * element of the table if you wish. * * To use this sort macro, you must COPY it into your program * in the 77 LEVEL area. Four (4) variables will be declared * and the $DEFINE for %SORTTABLE will be defined. * * Before invoking this macro you must set N-SUB to the * highest table element to be sorted. 77 I-SUB PIC S9(4) COMP. 77 J-SUB PIC S9(4) COMP. 77 M-SUB PIC S9(4) COMP. 77 N-SUB PIC S9(4) COMP. $DEFINE %SORTTABLE= IF N-SUB > 1 MOVE N-SUB TO M-SUB PERFORM TEST AFTER UNTIL M-SUB = 1 DIVIDE 2 INTO M-SUB ADD 1 TO M-SUB GIVING I-SUB PERFORM UNTIL I-SUB > N-SUB MOVE !1(I-SUB) TO !2 MOVE I-SUB TO J-SUB SUBTRACT M-SUB FROM J-SUB GIVING TALLY PERFORM UNTIL J-SUB <= M-SUB OR !1(TALLY) <= !2 MOVE !1(TALLY) TO !1(J-SUB) SUBTRACT M-SUB FROM J-SUB SUBTRACT M-SUB FROM J-SUB GIVING TALLY END-PERFORM MOVE !2 TO !1(J-SUB) ADD 1 TO I-SUB END-PERFORM END-PERFORM END-IF# MOVE number-of-elements to N-SUB. %SORTTABLE(TABLE-NAME#, HOLD-AREA#). Figure 6: WORKING-STORAGE SECTION. EXEC SQL INCLUDE SQLCA END-EXEC. * * * * * * BEGIN HOST VARIABLE DECLARATIONS * * * * * * * EXEC SQL BEGIN DECLARE SECTION END-EXEC. 01 PARTNUMBER PIC X(16). 01 PARTNAME PIC X(30). 01 SALESPRICE PIC S9(8)V99 COMP-3. 01 SALESPRICEIND SQLIND. 01 SQLMESSAGE PIC X(132). EXEC SQL END DECLARE SECTION END-EXEC. * * * * * * END OF HOST VARIABLE DECLARATIONS * * * * * * * 77 DONE-FLAG PIC X(01) VALUE 'N'. 88 NOT-DONE VALUE 'N'. 88 DONE VALUE 'Y'. 77 ABORT-FLAG PIC X(01) VALUE 'N'. 88 NOT-STOP VALUE 'N'. 88 ABORT VALUE 'Y'. 01 DEADLOCK PIC S9(9) COMP VALUE -14024. 01 RESPONSE. 05 RESPONSE-PREFIX PIC X(01) VALUE SPACE. 05 FILLER PIC X(15) VALUE SPACES. 01 DOLLARS PIC $$$,$$$,$$$.99. PROCEDURE DIVISION. A100-MAIN. DISPLAY "Program to SELECT specified rows from " "the Parts Table - COBEX2". DISPLAY " ". DISPLAY "Event List:". DISPLAY " Connect to PartsDBE". DISPLAY " Begin Work". DISPLAY " SELECT specified Part Number from the " "Parts Table until user enters '/' ". DISPLAY " Commit Work". DISPLAY " Disconnect from PartsDBE". DISPLAY " ". OPEN OUTPUT CRT. PERFORM A200-CONNECT-DBENVIRONMENT THRU A200-EXIT. PERFORM B100-SELECT-DATA THRU B100-EXIT UNTIL DONE. PERFORM A500-TERMINATE-PROGRAM THRU A500-EXIT. A100-EXIT. EXIT. A200-CONNECT-DBENVIRONMENT. EXEC SQL WHENEVER SQLERROR GO TO S300-SERIOUS-ERROR END-EXEC. DISPLAY "Connect to PartsDBE". EXEC SQL CONNECT TO 'PartsDBE' END-EXEC. A200-EXIT. EXIT. A300-BEGIN-TRANSACTION. DISPLAY "Begin Work". EXEC SQL BEGIN WORK END-EXEC. A300-EXIT. EXIT. A400-END-TRANSACTION. DISPLAY "Commit Work". EXEC SQL COMMIT WORK END-EXEC. A400-EXIT. EXIT. A500-TERMINATE-PROGRAM. EXEC SQL RELEASE END-EXEC. STOP RUN. A500-EXIT. EXIT. B100-SELECT-DATA. MOVE SPACES TO RESPONSE. MOVE "Enter Part Number or '/' to STOP> " TO PROMPT. WRITE PROMPT AFTER ADVANCING 1 LINE. ACCEPT RESPONSE. IF RESPONSE-PREFIX = "/" MOVE "Y" TO DONE-FLAG GO TO B100-EXIT ELSE MOVE RESPONSE TO PARTNUMBER. EXEC SQL WHENEVER SQLERROR GO TO S400-SQL-ERROR END-EXEC. EXEC SQL WHENEVER SQLWARNING GO TO S500-SQL-WARNING END-EXEC. EXEC SQL WHENEVER NOT FOUND GO TO S600-NOT-FOUND END-EXEC. DISPLAY "SELECT PartNumber, PartName and SalesPrice". PERFORM A300-BEGIN-TRANSACTION THRU A300-EXIT. EXEC SQL SELECT PARTNUMBER, PARTNAME, SALESPRICE INTO :PARTNUMBER, :PARTNAME, :SALESPRICE :SALESPRICEIND FROM PURCHDB.PARTS WHERE PARTNUMBER = :PARTNUMBER END-EXEC. PERFORM A400-END-TRANSACTION THRU A400-EXIT. PERFORM B200-DISPLAY-ROW THRU B200-EXIT. B100-EXIT. EXIT. B200-DISPLAY-ROW. DISPLAY " ". DISPLAY " Part Number: " PARTNUMBER. DISPLAY " Part Name: " PARTNAME. IF SALESPRICEIND < 0 DISPLAY " Sales Price is NULL" ELSE MOVE SALESPRICE TO DOLLARS DISPLAY " Sales Price: " DOLLARS. B200-EXIT. EXIT.