Recently, COBOL has been in the news as the State of New Jersey has asked for help with a COBOL-based system for unemployment claims. The system has come under heavy load because of the societal effects of the SARS-CoV-2 virus. This appears to have prompted IBM to offer free online COBOL training.
As old as COBOL is (60 years old this month), it is still heavily used in information management systems and pretty much anywhere there’s an IBM mainframe around. Three years ago Thomson Reuters reported that COBOL is used in 43% of banking systems, is behind 80% of in-person financial transactions and 95% of times an ATM card is used. They also reported 100s of billions of lines of running COBOL.
COBOL is often a source of amusement for programmers because it is seen as old, verbose, clunky, and difficult to maintain. And it’s often the case that people making the jokes have never actually written any COBOL. We plan to give them a chance: COBOL can now be used to write code for Cloudflare’s serverless platform Workers.
Here’s a simple “Hello, World!” program written in COBOL and accessible at https://hello-world.cobol.workers.dev/. It doesn’t do much--it just outputs “Hello, World!”--but it does it using COBOL.
IDENTIFICATION DIVISION.
PROGRAM-ID. HELLO-WORLD.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 HTTP_OK PIC X(4) VALUE "200".
01 OUTPUT_TEXT PIC X(14) VALUE "Hello, World!".
PROCEDURE DIVISION.
CALL "set_http_status" USING HTTP_OK.
CALL "append_http_body" USING OUTPUT_TEXT.
STOP RUN.
If you’ve never seen a COBOL program before, it might look very odd. The language emerged in 1960 from the work of a committee designing a language for business (COBOL = COmmon Business Oriented Language) and was intended to be easy to read and understand (hence the verbose syntax). It was partly based on an early language called FLOW-MATIC created by Grace Hopper.
IDENTIFICATION DIVISION.
To put COBOL in context: FORTRAN arrived in 1957, LISP and ALGOL in 1958, APL in 1962 and BASIC in 1964. The C language didn’t arrive on scene until 1972. The late 1950s and early 1960s saw a huge amount of work on programming languages, some coming from industry (such as FORTRAN and COBOL) and others from academia (such as LISP and ALGOL).
COBOL is a compiled language and can easily be compiled to WebAssembly and run on Cloudflare Workers. If you want to get started with COBOL, the GNUCobol project is a good place to begin.
Here’s a program that waits for you to press ENTER and then adds up the numbers 1 to 1000 and outputs the result:
IDENTIFICATION DIVISION.
PROGRAM-ID. ADD.
ENVIRONMENT DIVISION.
DATA DIVISION.
WORKING-STORAGE SECTION.
77 IDX PICTURE 9999.
77 SUMX PICTURE 999999.
77 X PICTURE X.
PROCEDURE DIVISION.
BEGIN.
ACCEPT X.
MOVE ZERO TO IDX.
MOVE ZERO TO SUMX.
PERFORM ADD-PAR UNTIL IDX = 1001.
DISPLAY SUMX.
STOP RUN.
ADD-PAR.
COMPUTE SUMX = SUMX + IDX.
ADD 1 TO IDX.
You can compile it and run it using GNUCobol like this (I put this in a file called terminator.cob
)
$ cobc -x terminator.cob
$ ./terminator
500500
$
cobc
compiles the COBOL program to an executable file. It can also output a C file containing C code to implement the COBOL program:
$ cobc -C -o terminator.c -x terminator.cob
This .c
file can then be compiled to WebAssembly. I’ve done that and placed this program (with small modifications to make it output via HTTP, as in the Hello, World! program above) at https://terminator.cobol.workers.dev/. Note that the online version doesn’t wait for you to press ENTER, it just does the calculation and gives you the answer.
DATA DIVISION.
You might be wondering why I called this terminator.cob
. That’s because this is part of the code that appears in The Terminator, James Cameron’s 1984 film. The film features a ton of code from the Apple ][ and a little snippet of COBOL (see the screenshot from the film below).
The screenshot shows the view from one of the HK-Aerial hunter-killer VTOL craft used by Skynet to try to wipe out the remnants of humanity. Using COBOL.
You can learn all about that in this Behind The Screens video I produced:
For those of you of the nerdy persuasion, here’s the original code as it appeared in the May 1984 edition of “73 Magazine” and was copied to look cool on screen in The Terminator.
If you want to scale your own COBOL-implemented Skynet, it only takes a few steps to convert COBOL to WebAssembly and have it run in over 200 cities worldwide on Cloudflare’s network.
PROCEDURE DIVISION.
Here’s how you can take your COBOL program and turn it into a Worker.
There are multiple compiler implementations of the COBOL language and a few of them are proprietary. We decided to use GnuCOBOL (formerly OpenCOBOL) because it's free software.
Given that Cloudflare Workers supports WebAssembly, it sounded quite straightforward: GnuCOBOL can compile COBOL to C and Emscripten compiles C/C++ to WebAssembly. However, we need to make sure that our WebAssembly binary is as small and fast as possible to maximize the time for user-code to run instead of COBOL's runtime.
GnuCOBOL has a runtime library called libcob, which implements COBOL's runtime semantics, using GMP (GNU Multiple Precision Arithmetic Library) for arithmetic. After we compiled both these libraries to WebAssembly and linked against our compiled COBOL program, we threw the WebAssembly binary in a Cloudflare Worker.
It was too big and it hit the CPU limit (you can find Cloudflare Worker’s limits here), so it was time to optimize.
GMP turns out to be a big library, but luckily for us someone made an optimized version for JavaScript (https://github.com/kripken/gmp.js), which was much smaller and reduced the WebAssembly instantiation time. As a side note, it's often the case that functions implemented in C could be removed in favour of a JavaScript implementation already existing on the web platform. But for this project we didn’t want to rewrite GMP.
While Emscripten can emulate a file system with all its syscalls, it didn't seem necessary in a Cloudflare Worker. We patched GnuCOBOL to remove the support for local user configuration and other small things, allowing us to remove the emulated file system.
The size of our Wasm binary is relatively small compared to other languages. For example, around 230KB with optimization enabled for the Game of Life later in this blog post.
Now that we have a COBOL program running in a Cloudflare Worker, we still need a way to generate an HTTP response.
The HTTP response generation and manipulation is written in JavaScript (for now... some changes to WebAssembly are currently being discussed that would allow a better integration). Emscripten imports these functions and makes them available in C, and finally we link all the C code with our COBOL program. COBOL already has good interoperability with C code.
As an example, we implemented the rock-paper-scissors game (https://github.com/cloudflare/cobol-worker/blob/master/src/worker.cob). See the full source (https://github.com/cloudflare/cobol-worker).
Our work can be used by anyone wanting to compile COBOL to WebAssembly; the toolchain we used is available on GitHub (https://github.com/cloudflare/cobaul) and is free to use.
To deploy your own COBOL Worker, you can run the following commands. Make sure that you have wrangler installed on your machine (https://github.com/cloudflare/wrangler).
wrangler generate cobol-worker [https://github.com/cloudflare/cobol-worker-template](https://github.com/cloudflare/cobol-worker-template)
It will generate a cobol-worker directory containing the Worker. Follow the instructions in your terminal to configure your Cloudflare account with wrangler.
Your worker is ready to go; enter npm run deploy
and once deployed the URL will be displayed in the console.
STOP RUN.
I am very grateful to Sven Sauleau for doing the work to make it easy to port a COBOL program into a Workers file and for writing the PROCEDURE DIVISION section above and to Dane Knecht for suggesting Conway’s Game of Life.
Cloudflare Workers with WebAssembly is an easy-to-use serverless platform that’s fast and cheap and scalable. It supports a wide variety of languages--including COBOL (and C, C++, Rust, Go, JavaScript, etc.). Give it a try today.
AFTERWORD
We learnt the other day of the death of John Conway who is well known for Conway’s Game of Life. In tribute to Conway, XKCD dedicated a cartoon:
I decided to implement the Game of Life in COBOL and reproduce the cartoon.
Here’s the code:
IDENTIFICATION DIVISION.
PROGRAM-ID. worker.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 PARAM-NAME PIC X(7).
01 PARAM-VALUE PIC 9(10).
01 PARAM-OUTPUT PIC X(10).
01 PARAM PIC 9(10) BINARY.
01 PARAM-COUNTER PIC 9(2) VALUE 0.
01 DREW PIC 9 VALUE 0.
01 TOTAL-ROWS PIC 9(2) VALUE 20.
01 TOTAL-COLUMNS PIC 9(2) VALUE 15.
01 ROW-COUNTER PIC 9(2) VALUE 0.
01 COLUMN-COUNTER PIC 9(2) VALUE 0.
01 OLD-WORLD PIC X(300).
01 NEW-WORLD PIC X(300).
01 CELL PIC X(1) VALUE "0".
01 X PIC 9(2) VALUE 0.
01 Y PIC 9(2) VALUE 0.
01 POS PIC 9(3).
01 ROW-OFFSET PIC S9.
01 COLUMN-OFFSET PIC S9.
01 NEIGHBORS PIC 9 VALUE 0.
PROCEDURE DIVISION.
CALL "get_http_form" USING "state" RETURNING PARAM.
IF PARAM = 1 THEN
PERFORM VARYING PARAM-COUNTER FROM 1 BY 1 UNTIL PARAM-COUNTER > 30
STRING "state" PARAM-COUNTER INTO PARAM-NAME
CALL "get_http_form" USING PARAM-NAME RETURNING PARAM-VALUE
COMPUTE POS = (PARAM-COUNTER - 1) * 10 + 1
MOVE PARAM-VALUE TO NEW-WORLD(POS:10)
END-PERFORM
ELSE
MOVE "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001110000000000001010000000000001010000000000000100000000000101110000000000010101000000000000100100000000001010000000000001010000000000000000000000000000000000000000000000000000000000000000000" TO NEW-WORLD.
PERFORM PRINT-WORLD.
MOVE NEW-WORLD TO OLD-WORLD.
PERFORM VARYING ROW-COUNTER FROM 1 BY 1 UNTIL ROW-COUNTER > TOTAL-ROWS
PERFORM ITERATE-CELL VARYING COLUMN-COUNTER FROM 1 BY 1 UNTIL COLUMN-COUNTER > TOTAL-COLUMNS
END-PERFORM.
PERFORM PRINT-FORM.
STOP RUN.
ITERATE-CELL.
PERFORM COUNT-NEIGHBORS.
COMPUTE POS = (ROW-COUNTER - 1) * TOTAL-COLUMNS + COLUMN-COUNTER.
MOVE OLD-WORLD(POS:1) TO CELL.
IF CELL = "1" AND NEIGHBORS < 2 THEN
MOVE "0" TO NEW-WORLD(POS:1).
IF CELL = "1" AND (NEIGHBORS = 2 OR NEIGHBORS = 3) THEN
MOVE "1" TO NEW-WORLD(POS:1).
IF CELL = "1" AND NEIGHBORS > 3 THEN
MOVE "0" TO NEW-WORLD(POS:1).
IF CELL = "0" AND NEIGHBORS = 3 THEN
MOVE "1" TO NEW-WORLD(POS:1).
COUNT-NEIGHBORS.
MOVE 0 TO NEIGHBORS.
PERFORM COUNT-NEIGHBOR
VARYING ROW-OFFSET FROM -1 BY 1 UNTIL ROW-OFFSET > 1
AFTER COLUMN-OFFSET FROM -1 BY 1 UNTIL COLUMN-OFFSET > 1.
COUNT-NEIGHBOR.
IF ROW-OFFSET <> 0 OR COLUMN-OFFSET <> 0 THEN
COMPUTE Y = ROW-COUNTER + ROW-OFFSET
COMPUTE X = COLUMN-COUNTER + COLUMN-OFFSET
IF X >= 1 AND X <= TOTAL-ROWS AND Y >= 1 AND Y <= TOTAL-COLUMNS THEN
COMPUTE POS = (Y - 1) * TOTAL-COLUMNS + X
MOVE OLD-WORLD(POS:1) TO CELL
IF CELL = "1" THEN
COMPUTE NEIGHBORS = NEIGHBORS + 1.
PRINT-FORM.
CALL "append_http_body" USING "<form name=frm1 method=POST><input type=hidden name=state value=".
CALL "append_http_body" USING DREW.
CALL "append_http_body" USING ">".
PERFORM VARYING PARAM-COUNTER FROM 1 BY 1 UNTIL PARAM-COUNTER > 30
CALL "append_http_body" USING "<input type=hidden name=state"
CALL "append_http_body" USING PARAM-COUNTER
CALL "append_http_body" USING " value="
COMPUTE POS = (PARAM-COUNTER - 1) * 10 + 1
MOVE NEW-WORLD(POS:10) TO PARAM-OUTPUT
CALL "append_http_body" USING PARAM-OUTPUT
CALL "append_http_body" USING ">"
END-PERFORM
CALL "append_http_body" USING "</form>".
PRINT-WORLD.
MOVE 0 TO DREW.
CALL "set_http_status" USING "200".
CALL "append_http_body" USING "<html><body onload='setTimeout(function() { document.frm1.submit() }, 1000)'>"
CALL "append_http_body" USING "<style>table { background:-color: white; } td { width: 10px; height: 10px}</style>".
CALL "append_http_body" USING "<table>".
PERFORM PRINT-ROW VARYING ROW-COUNTER FROM 3 BY 1 UNTIL ROW-COUNTER >= TOTAL-ROWS - 1.
CALL "append_http_body" USING "</table></body></html>".
PRINT-ROW.
CALL "append_http_body" USING "<tr>".
PERFORM PRINT-CELL VARYING COLUMN-COUNTER FROM 3 BY 1 UNTIL COLUMN-COUNTER >= TOTAL-COLUMNS - 1.
CALL "append_http_body" USING "</tr>".
PRINT-CELL.
COMPUTE POS = (ROW-COUNTER - 1) * TOTAL-COLUMNS + COLUMN-COUNTER.
MOVE NEW-WORLD(POS:1) TO CELL.
IF CELL = "1" THEN
MOVE 1 TO DREW
CALL "append_http_body" USING "<td bgcolor=blue></td>".
IF CELL = "0" THEN
CALL "append_http_body" USING "<td></td>".
If you want to run your own simulation you can do an HTTP POST with 30 parameters that when concatenated form the layout of the 15x20 world simulated in COBOL.
If you want to install this yourself, take the following steps:
Sign up for Cloudflare
Sign up for a workers.dev subdomain. I've already grabbed
cobol.workers.dev
, but imagine you’ve managed to grabmy-cool-name.workers.dev
Install wrangler, Cloudflare’s CLI for deploying Workers
Create a new COBOL Worker using the template
wrangler generate cobol-worker https://github.com/cloudflare/cobol-worker-template
Configure
wrangler.toml
to point to your account and set a name for this project, let’s saymy-first-cobol
.Grab the files
src/index.js
andsrc/worker.cob
from my repo here: https://github.com/jgrahamc/game-of-life and replace them in thecobol-worker
.npm run deploy
The COBOL Worker will be running at
https://my-first-cobol.my-cool-name.workers.dev/