I am finally to my goal made at the beginning of this (lousy) year! Use embedded SQL to extract data from a database and the COBOL report writer to produce a printed report. Thus, this is the last planned post for GnuCOBOL.
In 1979 when I started my career, I was a programmer for (what is now) Texas Statue University’s Administrative Data Processing department. Paper reports were big and even bigger at the university where few humans in the Admin dept had access to a terminal.
A year later I went on to a much better paid position (the university could get by with paying less than minimum wage) where I used COBOL on a Hewlett-Packard HP3000 which was my machine of choice for the rest of my professional programming career. UNFORTUNATELY, HP decided not to implement the Report Writer module (the Report Writer was an optional module in the COBOL standard).
I can remember banging my head on the wall because it was extremely boring to have to manage the details of writing a report by hand. As time went by I eventually completely forgot how the Report Writer even works, but I never forgot I would prefer to have it!
To write the test program for this post, I had to relearn the report writer. It really isn’t too difficult to do so. But you do have to learn quite a few things simultaneously to make it work.
Note, I had originally intended to also use SORT INPUT/OUTPUT PROCEDURES to sort the data as done in the prior post. My initial version of the program did that, but there was a lot more code than I wanted for an example. Given one most likely wouldn’t use COBOL SORT when extracting data from a database, I decided to forgo the COBOL SORT.
Resources
The GnuCOBOL manual (3.1) has an entire section (section 9) on how to use the Report Writer. I also found this tutorial quite useful.
It is written for IBM MVT COBOL, but there are few differences between that and GnuCOBOL.
The GnuCOBOL FAQ also has a section on the report writer, with an example derived from the above tutorial.
Designing The Report
As with the prior example, I want to design a DVD rental history report but now that I’m using the report writer, it will have headings, counts, and footings. A real report.
As I was working on this projects, I thought back to the president of the company I worked at long ago. He designed ALL reports and we programmers implemented them. The reports were the face of our company and he wanted them to look good. Indeed they did, his reports were probably the best I’ve seen. Especially these days when many reports are just an after thought.
When he gave us a report to implement it was on an official IBM Report Layout form like this (found at http://ibm-1401.info/IBM1401_ArchivePics.html)
Totally off topic, but notice the Carriage Control Tape column on the far left. When using old printers like the IBM 1403, you could advance to a particular line by if there was a punch in that tape. Channel 1 of the tape was always top of the form and typically we used used a tape with only that channel punched. But if you need to print a report that was largely empty space, like may be a check or a utility bill, you could very quickly slew to the line you needed by advancing to the appropriate channel. For example:
WRITE PRINT-REC AFTER ADVANCING TO-CHECK-AMT-LINE.
That old 1403 printer could print amazingly fast for something so large. And standing next to it was about like standing next to a gattling gun (or so it seems to me now).
Popping the stack back to my original train of thought: My input data is this data base:
I’ll use this query to extract the data:
select customer.customer_id, customer.last_name, customer.first_name, film.title, to_char(rental.return_date,'yyyymmdd') as sorteddate from customer inner join rental on customer.customer_id = rental.customer_id left join inventory on rental.inventory_id = inventory.inventory_id left join film on inventory.film_id = film.film_id order by customer.last_name, customer.first_name, sorteddate
and will produce a report that looks like this:
11/12/2020 PAGE: 1 ---------------------CUSTOMER HISTORY REPORT--------------------- ------------NAME------------ CUST ---DATE--- -----LAST------ ---FIRST---- -ID- -----DVD TITLE------ -RETURNED-
I will want to report the total DVDs each customer has rented, the total number of DVDs rented, and the total number of customers reported.
A Simpler Report Writer Program First
It took me some time to get my head around the operation of the report writer.It’s not hard, just different. nearly everything is specified in the DATA DIVISION not the PROCEDURE DIVISION.
I started by writing a program that didn’t do report control breaks. Omitting the control footings makes the reporting easier to understand.
The program source can be found at http://www.xyfyx.com/files/reportWriter01.cob
Here is notable parts of the code with comments:
Below is the output file that will contain the report. LINE SEQUENTIAL indicates when each line is written it should be terminated with the appropriate line terminator for the operating system being used.
ENVIRONMENT DIVISION. INPUT-OUTPUT SECTION. FILE-CONTROL. SELECT RF-REPORT-FILE, ASSIGN TO "./reportWriter01.lst", ORGANIZATION IS LINE SEQUENTIAL. ... FD RF-REPORT-FILE, REPORT IS RF-REPORT.
These fields are used to format dates:
WORKING-STORAGE SECTION. ... 01 DB-REC. 03 DB-CUSTID PIC 9(9). 03 DB-LASTNAME PIC X(45). 03 DB-FIRSTNAME PIC X(45). 03 DB-FILMTITLE PIC X(45). 03 DB-RETURNDATE PIC 99999999. ... 01 TF-TEMP-FIELDS. 03 TF-DATE-IN. 05 TF-YY PIC 9999. 05 TF-MM PIC 99. 05 TF-DD PIC 99. 03 TF-DATE-OUT PIC X(10). 03 TF-RUNDATE-IN. 05 TF-RUNDATE-YY PIC 9999. 05 TF-RUNDATE-MM PIC 99. 05 TF-RUNDATE-DD PIC 99. 03 TF-RUNDATE-OUT PIC X(10). ...
At the end of the DATA DIVISION is the REPORT SECTION which will describe (usually) everything needed to produce the report.
- Page Limit: Number of lines per page.
- Heading: line upon which the first header line is printed.
- First Detail: line upon which the first detail line is printed.
- Last detail: line upon which the last detail line of the page can be printed.
REPORT SECTION. RD RF-REPORT, PAGE LIMIT 66 LINES, HEADING 1, FIRST DETAIL 7, LAST DETAIL 60.
This next section defines the header lines to be printed. The first line is on the absolute position of LINE 1, then each line after is placed on the next physical line (LINE PLUS 1) or 2 lines down (LINE PLUS 2).
Within each line are column definitions. For this report I’m specifying exact column placement. SOURCE indicated this column will contain the specified field using the specified PIC. So the report’s run date starts in column 1, comes from TF-RUNDATE-OUT and will take 20 characters.
The constant “PAGE” is placed at column 58. Note that you do not have to use PIC for values. The next example will show a more concise page definition.
01 PAGE-HEAD-GROUP TYPE PAGE HEADING. 03 LINE 1. 05 COLUMN 1 PIC X(20), SOURCE TF-RUNDATE-OUT. 05 COLUMN 58 PIC X(6), VALUE "PAGE: ". 05 COLUMN 64 PIC Z9, SOURCE PAGE-COUNTER. 03 LINE PLUS 1. 05 COLUMN 1 PIC X(21), VALUE ALL "-". 05 COLUMN 22 PIC X(23), VALUE "CUSTOMER HISTORY REPORT". 05 COLUMN 45 PIC X(21), VALUE ALL "-". 03 LINE PLUS 2. 05 COLUMN 1 PIC X(28), VALUE "------------NAME------------". 05 COLUMN 30 PIC XXXX, VALUE "CUST". 05 COLUMN 56 PIC X(10), VALUE "---DATE---". 03 LINE PLUS 1. 05 COLUMN 1 PIC X(15), VALUE "-----LAST------". 05 COLUMN 17 PIC X(12), VALUE "---FIRST----". 05 COLUMN 30 PIC XXXX, VALUE "-ID-". 05 COLUMN 35 PIC X(20), VALUE "-----FILM TITLE-----". 05 COLUMN 56 PIC X(10), VALUE "-RETURNED-".
The detail line is laid out in the same manner. Each field is SOURCEd from the database record (except the date).
01 DETAIL-LINE TYPE DETAIL. 03 LINE PLUS 1. 05 COLUMN 1 PIC X(15), SOURCE DB-LASTNAME. 05 COLUMN 17 PIC X(12), SOURCE DB-FIRSTNAME. 05 COLUMN 30 PIC ZZZ9, SOURCE DB-CUSTID. 05 COLUMN 35 PIC X(20), SOURCE DB-FILMTITLE. 05 COLUMN 56 PIC X(10), SOURCE TF-DATE-OUT. ...
In the procedure division, the RUN DATE is derived from the system date:
ACCEPT TF-RUNDATE-IN FROM DATE YYYYMMDD. STRING TF-RUNDATE-MM, "/", TF-RUNDATE-DD, "/", TF-RUNDATE-YY INTO TF-RUNDATE-OUT. ...
After the cursor is setup, we are ready to begin reading records and printing them. DON’T forget to open the report file (I did at first. No error is generated, but I couldn’t find any output).
The INITIATE verb initiates the report.
OPEN OUTPUT RF-REPORT-FILE. INITIATE RF-REPORT. ...
This is the “heart” of printing. Each line is read from the database, and we simply GENERATE-DETAIL line to print the report – it handles all of the details of printing for us.
PERFORM UNTIL SQLCODE NOT = ZERO, ... GENERATE DETAIL-LINE; EXEC SQL FETCH C1 INTO :DB-CUSTID, :DB-LASTNAME, :DB-FIRSTNAME, :DB-FILMTITLE, :DB-RETURNDATE END-EXEC; END-PERFORM. ...
We’ve read all of the data, so terminate the report, and close it.
TERMINATE RF-REPORT. CLOSE RF-REPORT-FILE. ...
As you can see ALL of the work of generating the report is setting up the REPORT SECTION. Even without the report writer you still have to define how the output will appear, so there isn’t much extra necessary to use the report writer.
Compile and run:
$export COBCPY=~/Open-COBOL-ESQL-1.2/copy $export COB_LDFLAGS=-Wl,--no-as-needed $ocesql reportWriter01.cob reportWriter01.tmp precompile start: reportWriter01.cob ======================================================= LIST OF CALLED DB Library API ======================================================= ; ; ; ; ; ; Generate:OCESQLConnect Generate:OCESQLCursorDeclare Generate:OCESQLCursorOpen Generate:OCESQLCursorFetchOne Generate:OCESQLCursorFetchOne Generate:OCESQLCursorClose Generate:OCESQLDisconnect Generate:ROLLBACK ======================================================= $cobc -locesql -x reportWriter01.tmp
Excerpts from the output file:
less reportWriter01.lst 11/11/2020 PAGE: 1 ---------------------CUSTOMER HISTORY REPORT--------------------- ------------NAME------------ CUST ---DATE--- -----LAST------ ---FIRST---- -ID- -----FILM TITLE----- -RETURNED- Abney Rafael 505 Sagebrush Clueless 05/29/2005 Abney Rafael 505 Pocus Pulp 06/05/2005 Abney Rafael 505 Legally Secretary 06/19/2005 Abney Rafael 505 Nightmare Chill 06/20/2005 Abney Rafael 505 Trading Pinocchio 06/28/2005 Abney Rafael 505 Coneheads Smoochy 06/28/2005 Abney Rafael 505 Wanda Chamber 07/12/2005 Abney Rafael 505 Madness Attacks 07/14/2005 Abney Rafael 505 Conquerer Nuts 07/14/2005 ... Adams Kathleen 36 Go Purple 06/20/2005 Adams Kathleen 36 Betrayed Rear 07/10/2005 Adams Kathleen 36 Room Roman 07/11/2005 11/11/2020 PAGE: 2 ---------------------CUSTOMER HISTORY REPORT--------------------- ------------NAME------------ CUST ---DATE--- -----LAST------ ---FIRST---- -ID- -----FILM TITLE----- -RETURNED- Adams Kathleen 36 Boogie Amelie 07/12/2005 Adams Kathleen 36 Swarm Gold 07/12/2005 Adams Kathleen 36 Amadeus Holy 07/16/2005 ... 11/11/2020 PAGE: 98 ---------------------CUSTOMER HISTORY REPORT--------------------- ------------NAME------------ CUST ---DATE--- -----LAST------ ---FIRST---- -ID- -----FILM TITLE----- -RETURNED- Young Cynthia 28 Ice Crossing 08/23/2005 Young Cynthia 28 Saddle Antitrust 08/24/2005 Young Cynthia 28 Lebowski Soldiers 08/27/2005 Young Cynthia 28 Loverboy Attacks 08/27/2005 Young Cynthia 28 Attacks Hate 08/28/2005 Young Cynthia 28 Suspects Quills 00/00/0000
The Final Version of the Program
The above report handles the header and detail lines great. Now I want to add in control breaks to report the number of DVDs each customer has rented, and at the end the total number of DVDs rented and the total number of customers reported.
Unfortunately there was no dollar amounts in this data upon which to report. The report writer can handle totaling detail line amounts with almost no more work than the above report by just using the SUM clause.
Instead, I want to count records which will add just a slight bit more complexity.
The source to this program can be found at http://www.xyfyx.com/files/reportWriter02.cob.
Here are notable parts of the code with comments:
IDENTIFICATION DIVISION. ... WORKING-STORAGE SECTION.
I’m going to need CS-1, a constant of ONE used to add each DVD detail printed. I’m also going to need a counter to track the total number of customers reported.
01 CS-CONSTANTS. 03 CS-1 PIC S9(4), COMP VALUE 1. 01 CT-COUNTERS. 03 CT-CUSTS PIC S9(9), COMP VALUE ZERO. ...
I made a couple of changes for the database record read. I added DB-CUSTNAME. I need to know if either DB-LASTNAME or DB-FIRSTNAME changes, so I grouped them into DB-CUSTNAME.
I also altered how I handle DB-RETURNDATE. I want to STRING month, day, year together but OCESQL requires that the field containing the date from the database be an elementary item. To more cleanly handle this, I REDEFINE DB-RETURNDATE which allows access to the individual fields.
01 DB-REC. 03 DB-CUSTID PIC 9(9). 03 DB-CUSTNAME. 05 DB-LASTNAME PIC X(45). 05 DB-FIRSTNAME PIC X(45). 03 DB-DVDTITLE PIC X(45). 03 DB-RETURNDATE PIC 99999999. 03 FILLER REDEFINES DB-RETURNDATE. 05 DB-YYYY PIC 9999. 05 DB-MM PIC 99. 05 DB-DD PIC 99. ... * --------------------------------------------------------------- REPORT SECTION.
In the RD, I now have the controls FINAL and DB-CUSTNAME. Every time DB-CUSTNAME changes a control break occurs. Also at the end of the report (FINAL) a control break occurs.
RD RF-REPORT, CONTROLS ARE FINAL, DB-CUSTNAME, PAGE LIMIT 60 LINES, HEADING 1, FIRST DETAIL 7, LAST DETAIL 60. ...
I made a slight change to the header. Line 1 now contains a form feed character which will allow it to print on pretty much any modern printer.
01 PAGE-HEAD-GROUP TYPE PAGE HEADING. 03 LINE 1. *> *** FORMFEED 05 COLUMN 1 VALUE X'0C'. 03 LINE PLUS 1. 05 COLUMN 1 PIC X(20), SOURCE TF-RUNDATE-OUT. 05 COLUMN 58 VALUE "PAGE: ". 05 COLUMN 64 PIC Z9, SOURCE PAGE-COUNTER.
In this report, I drop absolute column positions (except COLUMN 1) and use relative (PLUS n). In this next section, each field is adjacent to the next so I use PLUS 1. Typically I would want a space between columns and in the detail line you will see everything set at PLUS 2.
Also note that the PIC clause is now omitted as well. If omitted, the compiler derives the length from the VALUE clause.
03 LINE PLUS 1. 05 COLUMN 1 PIC X(21), VALUE ALL "-". 05 COLUMN PLUS 1 VALUE "CUSTOMER HISTORY REPORT". 05 COLUMN PLUS 1 PIC X(21), VALUE ALL "-". 03 LINE PLUS 2. 05 COLUMN 1 VALUE "------------". 05 COLUMN PLUS 1 VALUE "NAME------------". 05 COLUMN PLUS 2 VALUE "CUST". 05 COLUMN 56 VALUE "---DATE---". 03 LINE PLUS 1. 05 COLUMN 1 VALUE "-----LAST------". 05 COLUMN PLUS 2 VALUE "---FIRST----". 05 COLUMN PLUS 2 VALUE "-ID-". 05 COLUMN PLUS 2 VALUE "-----DVD TITLE------". 05 COLUMN PLUS 2 VALUE "-RETURNED-".
The DETAIL-LINE is very nearly like the last report. Each column contains the appropriate PIC clause to format the data, a SOURCE clause indicating where to obtain the data, and a relative column position.
Note the use of GROUP INDICATE. This clause causes the associated field to be omitted after the first time it is printed on each page. This makes the report much easier to read and saves some ink as well.
01 DETAIL-LINE TYPE DETAIL. 03 LINE PLUS 1. 05 COLUMN 1 PIC X(15), SOURCE DB-LASTNAME, GROUP INDICATE. *> PRINTS ONLY ONCE 05 COLUMN PLUS 2 PIC X(12), SOURCE DB-FIRSTNAME, GROUP INDICATE. 05 COLUMN PLUS 2 PIC ZZZ9, SOURCE DB-CUSTID, GROUP INDICATE. 05 COLUMN PLUS 2 PIC X(20), SOURCE DB-DVDTITLE. 05 COLUMN PLUS 2 PIC X(10), SOURCE TF-DATE-OUT.
This is the footing group that will print at the end of each customer. In consists simply of a label and the number of DVDs rented.
The DVD count is obtained by using SUM CS-1. This will add 1 to an internal counter for each detail line printed for the customer. Had the database contained an amount field, say DB-AMOUNT, you could use SUM DB-AMOUNT and get the total amount for all records.
01 CUST-TOTAL TYPE CONTROL FOOTING DB-CUSTNAME, NEXT GROUP IS PLUS 2. 03 LINE PLUS 1. 05 COLUMN 35 VALUE "---CUSTOMER RENTALS:". 05 COLUMN 61 PIC Z,ZZ9, SUM CS-1. *> *** ADDING 1 PER RECORD
Here is the report totals print group (FINAL FOOTING).
The total DVDs rented is obtains in the same manner as above, by SUMming CS-1.
The customer count has to manually be calculated.
01 FINAL-GROUP TYPE CONTROL FOOTING FINAL. 03 LINE PLUS 2. 05 COLUMN 35 VALUE "------TOTAL RENTALS:". 05 COLUMN 59 PIC ZZZ,ZZ9, SUM CS-1. 03 LINE PLUS 2. 05 COLUMN 35 VALUE "----TOTAL CUSTOMERS:". 05 COLUMN 59 PIC ZZZ,ZZ9, SOURCE CT-CUSTS. * --------------------------------------------------------------- PROCEDURE DIVISION.
Here is how the customer count is calculated, in the DECLARATIVES.
This bit of code is executed before each CUST-TOTAL report footing (e.g. the end of each customer).
It simply adds 1 to CT-CUSTS to maintain a running count of customers encountered during the report print.
DECLARATIVES. 00 SECTION. USE BEFORE REPORTING CUST-TOTAL. 00-CUST-TOTAL. ADD 1 TO CT-CUSTS. END DECLARATIVES. ...
The report is generated in the same manner (with a slight change in how I used STRING to generate the date).
OPEN OUTPUT RF-REPORT-FILE. INITIATE RF-REPORT. PERFORM UNTIL SQLCODE NOT = ZERO, STRING DB-MM, "/", DB-DD, "/", DB-YYYY INTO TF-DATE-OUT; GENERATE DETAIL-LINE; EXEC SQL FETCH C1 INTO :DB-CUSTID, :DB-LASTNAME, :DB-FIRSTNAME, :DB-DVDTITLE, :DB-RETURNDATE END-EXEC; END-PERFORM. TERMINATE RF-REPORT. CLOSE RF-REPORT-FILE. ...
To compile and run the final report:
$export COBCPY=~/Open-COBOL-ESQL-1.2/copy $export COB_LDFLAGS=-Wl,--no-as-needed $ocesql reportWriter02.cob reportWriter02.tmp precompile start: reportWriter02.cob ======================================================= LIST OF CALLED DB Library API ======================================================= ; ; ; ; ; ; Generate:OCESQLConnect Generate:OCESQLCursorDeclare Generate:OCESQLCursorOpen Generate:OCESQLCursorFetchOne Generate:OCESQLCursorFetchOne Generate:OCESQLCursorClose Generate:OCESQLDisconnect Generate:ROLLBACK ======================================================= $cobc -locesql -x reportWriter02.tmp
Excerpts from the output file:
^L 11/12/2020 PAGE: 1 ---------------------CUSTOMER HISTORY REPORT--------------------- ------------NAME------------ CUST ---DATE--- -----LAST------ ---FIRST---- -ID- -----DVD TITLE------ -RETURNED- Abney Rafael 505 Sagebrush Clueless 05/29/2005 Pocus Pulp 06/05/2005 Legally Secretary 06/19/2005 Nightmare Chill 06/20/2005 Trading Pinocchio 06/28/2005 Coneheads Smoochy 06/28/2005 Wanda Chamber 07/12/2005 Madness Attacks 07/14/2005 Conquerer Nuts 07/14/2005 Double Wrath 07/16/2005 Goodfellas Salute 07/20/2005 Hobbit Alien 08/05/2005 Shock Cabin 08/06/2005 Karate Moon 08/08/2005 Juggler Hardly 08/10/2005 Strictly Scarface 08/20/2005 Blackout Private 08/23/2005 ... Freddy Storm 08/28/2005 Chocolat Harry 08/28/2005 Clash Freddy 08/28/2005 Conversation Downhil 00/00/0000 ---CUSTOMER RENTALS: 21 Adam Nathaniel 504 Kiss Glory 05/31/2005 Gathering Calendar 06/04/2005 Noon Papi 06/06/2005 Guys Falcon 06/26/2005 Shepherd Midsummer 06/27/2005 Ending Crowds 07/12/2005 Hanging Deep 07/13/2005 Chasing Fight 07/15/2005 Something Duck 07/15/2005 Nemo Campus 07/18/2005 Poseidon Forever 07/30/2005 Divorce Shining 07/30/2005 Jason Trap 08/01/2005 Sleuth Orient 08/02/2005 Tramp Others 08/03/2005 Tights Dawn 08/04/2005 Rocky War 08/07/2005 Amadeus Holy 08/10/2005 Lust Lock 08/21/2005 Wardrobe Phantom 08/22/2005 Menagerie Rushmore 08/24/2005 Analyze Hoosiers 08/24/2005 Dancing Fever 08/25/2005 Boogie Amelie 08/25/2005 Orient Closer 08/28/2005 War Notting 08/28/2005 Freddy Storm 08/30/2005 Strangers Graffiti 08/31/2005 ---CUSTOMER RENTALS: 28 ^L 11/12/2020 PAGE: 2 ---------------------CUSTOMER HISTORY REPORT--------------------- ------------NAME------------ CUST ---DATE--- -----LAST------ ---FIRST---- -ID- -----DVD TITLE------ -RETURNED- Adams Kathleen 36 Orange Grapes 05/28/2005 Alone Trip 06/01/2005 Go Purple 06/20/2005 Betrayed Rear 07/10/2005 Room Roman 07/11/2005 Boogie Amelie 07/12/2005 Swarm Gold 07/12/2005 Amadeus Holy 07/16/2005 Sling Luke 07/30/2005 Pianist Outfield 08/01/2005 Seabiscuit Punk 08/01/2005 Women Dorado 08/02/2005 Wash Heavenly 08/02/2005 Treatment Jekyll 08/03/2005 ... 11/12/2020 PAGE: 40 ---------------------CUSTOMER HISTORY REPORT--------------------- ------------NAME------------ CUST ---DATE--- -----LAST------ ---FIRST---- -ID- -----DVD TITLE------ -RETURNED- Young Cynthia 28 Ship Wonderland 05/31/2005 Star Operation 06/17/2005 Dying Maker 06/18/2005 Banger Pinocchio 06/23/2005 Odds Boogie 06/25/2005 Virginian Pluto 06/26/2005 Wolves Desire 07/09/2005 Kick Savannah 07/10/2005 Deceiver Betrayed 07/12/2005 Dalmations Sweden 07/16/2005 Murder Antitrust 07/16/2005 Papi Necklace 07/18/2005 Spirit Flintstones 07/18/2005 Trading Pinocchio 08/01/2005 Wars Pluto 08/02/2005 Lawless Vision 08/03/2005 Clueless Bucket 08/03/2005 Birch Antitrust 08/05/2005 Easy Gladiator 08/05/2005 License Weekend 08/05/2005 Fiction Christmas 08/08/2005 Candidate Perdition 08/09/2005 Translation Summer 08/19/2005 Minds Truman 08/21/2005 Beverly Outlaw 08/21/2005 Ice Crossing 08/23/2005 Saddle Antitrust 08/24/2005 Lebowski Soldiers 08/27/2005 Loverboy Attacks 08/27/2005 Attacks Hate 08/28/2005 Suspects Quills 00/00/0000 ---CUSTOMER RENTALS: 32 ------TOTAL RENTALS: 16,044 ----TOTAL CUSTOMERS: 599
Being the paranoid programmer I am, all totals were compared with the database and they match!
This concludes my foray into GnuCOBOL!