DEV Community

Tomasz Wegrzanowski
Tomasz Wegrzanowski

Posted on

100 Languages Speedrun: Episode 50: COBOL

COBOL - Common Business Oriented Language - is one of the oldest programming languages. It was universally considered to be shit for pretty much its entire existence, because it was shit.

It's essentially dead. There are frequent claims in the media of "um, actually Cobol is super popular", but they're all bullshit journalists love to repeat after each other without any evidence - which is pretty much in line with the quality of tech journalism. You can easily see from stack overflow, any jobs website, or such, that the only thing people still Cobol for is keeping zombie system from passing away completely.

Just like Fortran, the language changed many times while keeping the name. I'll try to stick to classic Cobol from the punched card era, with its fixed column layout, even though many of less archaic dialects of Cobol allow a bit more flexibility.

Hello, World!

 * HELLO WORLD IN COBOL IDENTIFICATION DIVISION. PROGRAM-ID. HELLO. PROCEDURE DIVISION. DISPLAY 'HELLO WORLD'. STOP RUN. 
Enter fullscreen mode Exit fullscreen mode
$ cobc -xg hello.cob $ ./hello HELLO WORLD 
Enter fullscreen mode Exit fullscreen mode

That's some 2D picture.

Let's take it line by line:

  • IDENTIFICATION DIVISION. - some metadata about the procedure
  • PROGRAM-ID. Hello. - the name of the procedure
  • DATA DIVISION. - there's also one there usually, defines data
  • PROCEDURE DIVISION. - code of the procedure
  • DISPLAY 'HELLO WORLD. - displays a string and newline
  • STOP RUN. - exits the program

Everything in caps to keep with the spirit of Cobol.

And column by column:

  • first 6 columns are for statement numbers
  • column 7 is comment indicator - there are a few possible characters for it, we'll be using *
  • names, divisions etc. should start from column 8
  • everything else should start from column 12

Loop

Let's create a loop that prints a number from 1 to 20:

 IDENTIFICATION DIVISION. PROGRAM-ID. LOOP. DATA DIVISION. WORKING-STORAGE SECTION. 01 N PIC 9(2). PROCEDURE DIVISION. PERFORM VARYING N FROM 1 BY 1 UNTIL N > 20 DISPLAY N END-PERFORM. STOP RUN. 
Enter fullscreen mode Exit fullscreen mode

It works:

$ cobc -xg loop.cob $ ./loop 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 
Enter fullscreen mode Exit fullscreen mode

Line by line:

  • inside DATA DIVISION., there's WORKING-STORAGE SECTION. which defines all local variables
  • 01 N PIC 9(2). defines N as two digit decimal number
  • that 01 is about nested data definitions (so a 6 character date variable can contain inside it three 2 character variables for year, month, and day etc.), 01 means regular variable
  • PERFORM VARYING N FROM 1 BY 1 UNTIL N > 20 to END-PERFORM loops N from 1 to 20
  • DISPLAY N prints N, with leading zeroes

The Y2K panic was largely about such PIC 9(2)s used by old COBOL code from the 1960s and 1970s to define year fields, and similar poor practices in other systems. People pretty much forgot about Y2K panic by now, but it was a huge cultural phenomenon back then. Like most such panics, it was over pretty much nothing. After their predictions of doom all turned false, people tried to rewrite history and claim there weren't many bugs thanks to some heroic effort to preemptively fix them (Wikipedia still contains such lies). In reality, there wasn't much there to begin with, and it was just panic fueled by the media, and Big Tech of the day trying to sell Y2K preparedness services.

FIZZBUZZ

Let's write the FIZZBUZZ! As we're doing COBOL, it will be all caps and with leading zeroes.

 IDENTIFICATION DIVISION. PROGRAM-ID. FIZZBUZZ. DATA DIVISION. WORKING-STORAGE SECTION. 01 N PIC 9(3). 01 M PIC 9(3). 01 3REM PIC 9(1). 01 5REM PIC 9(1). PROCEDURE DIVISION. PERFORM VARYING N FROM 1 BY 1 UNTIL N > 100 DIVIDE N BY 3 GIVING M REMAINDER 3REM DIVIDE N BY 5 GIVING M REMAINDER 5REM EVALUATE 3REM ALSO 5REM WHEN ZERO ALSO ZERO DISPLAY 'FIZZBUZZ' WHEN ANY ALSO ZERO DISPLAY 'BUZZ' WHEN ZERO ALSO ANY DISPLAY 'FIZZ' WHEN OTHER DISPLAY N END-EVALUATE END-PERFORM. STOP RUN. 
Enter fullscreen mode Exit fullscreen mode
$ cobc -xg fizzbuzz.cob $ ./fizzbuzz 001 002 FIZZ 004 BUZZ FIZZ 007 008 FIZZ BUZZ 011 FIZZ 013 014 FIZZBUZZ 016 017 FIZZ 019 BUZZ ... 
Enter fullscreen mode Exit fullscreen mode

What's going on:

  • all variables defined to fit exact number of digits they need
  • variable names can start with numbers, something that's not really a thing in any language these days
  • EVALUATE 3REM ALSO 5REM is a case statement over two variables - interestingly a lot of languages don't really allow that. Apparently it was only added in COBOL 85 and not available earlier.
  • WHEN ZERO ALSO ANY etc. - are various matches, including wildcard matches

Fibonacci

This was surprisingly difficult to get working. I think a lot of functions like COMPUTE use newfangled COBOL features from the 1970s+. When I tried to do more "classic" COBOL, GnuCOBOL was not too happy about it. Then again, the original COBOL didn't even have recursion, so it's hard to pick up the right year to target.

Interesting COBOL feature is M PIC Z(8)9 - we're defining M as having 8 digits which should not be printed if zero (Z(8)), and one regular digit (9). Data storage and formatting are intertwined like this.

 * MAIN PROGRAM IDENTIFICATION DIVISION. PROGRAM-ID. FIBLOOP. ENVIRONMENT DIVISION. CONFIGURATION SECTION. REPOSITORY. FUNCTION FIB. DATA DIVISION. WORKING-STORAGE SECTION. 01 N PIC 9(3). 01 M PIC Z(8)9. PROCEDURE DIVISION. PERFORM VARYING N FROM 1 BY 1 UNTIL N > 20 COMPUTE M = FIB(N) DISPLAY 'FIB(' WITH NO ADVANCING DISPLAY N WITH NO ADVANCING DISPLAY ')=' WITH NO ADVANCING DISPLAY M END-PERFORM. STOP RUN. END PROGRAM FIBLOOP. * FUNCTION FIB(N) IDENTIFICATION DIVISION. FUNCTION-ID. FIB. DATA DIVISION. LOCAL-STORAGE SECTION. 01 A PIC 9(9). 01 B PIC 9(9). 01 N1 PIC 9(3). 01 N2 PIC 9(3). LINKAGE SECTION. 01 N PIC 9(3). 01 RESULT PIC 9(9) COMP BASED. PROCEDURE DIVISION USING N RETURNING RESULT. IF N IS LESS OR EQUAL TO 2 THEN MOVE 1 TO RESULT ELSE SUBTRACT 1 FROM N GIVING N1 SUBTRACT 2 FROM N GIVING N2 COMPUTE A = FIB(N1) COMPUTE B = FIB(N2) ADD A TO B GIVING RESULT END-IF. GOBACK. END FUNCTION FIB. 
Enter fullscreen mode Exit fullscreen mode
$ cobc -xg fib.cob $ ./fib FIB(001)= 1 FIB(002)= 1 FIB(003)= 2 FIB(004)= 3 FIB(005)= 5 FIB(006)= 8 FIB(007)= 13 FIB(008)= 21 FIB(009)= 34 FIB(010)= 55 FIB(011)= 89 FIB(012)= 144 FIB(013)= 233 FIB(014)= 377 FIB(015)= 610 FIB(016)= 987 FIB(017)= 1597 FIB(018)= 2584 FIB(019)= 4181 FIB(020)= 6765 
Enter fullscreen mode Exit fullscreen mode

There's a lot of weird stuff going on here. There's ENVIRONMENT DIVISION listing which functions we're using. There's LINKAGE SECTION for function's inputs and outputs.

Basically 90% of this trivial program is ridiculous boilerplate which you need to get just right. I wanted to check a bunch of examples of COBOL code, and every example used different boilerplate, and none would work on GnuCOBOL without serious tweaking - even ones tagged as being specifically for GnuCOBOL. The whole language is far more of a dumpster fire than I expected. Fortran was not amazing, but at least you could see imagine actual humans coding Fortran. I have no idea what kind of subterranean reptilian creatures from the Hollow Earth COBOL was designed for, because it sure as hell wasn't designed for any kind of human beings.

Should you use COBOL?

People knew COBOL was shit before Joe Biden was even born, so obviously no.

It might very well be the absolute worst language reviewed so far. Even Befunge and Thue were more enjoyable to code in.

Code

All code examples for the series will be in this repository.

Code for the COBOL episode is available here.

Top comments (0)