TOPS-10: Essex BCPL

BCPL - Basic Combined Programming Language - is probably best known as the ancestor of the C programming language made famous by Unix. But BCPL was an important and widely used language in its own right. First implemented by Martin Richards for CTSS, it spread to many different architectures during the 1970s, especially in the UK where it was used for teaching computer science, AI and in industry.

(Incidentally my first ever job, as an intern in 1990 working for the former British semiconductor company Inmos, was writing CAD tools using BCPL on a micro-VAX).

Essex BCPL for TOPS-10

The version we are looking at today for TOPS-10 is from the University of Essex and dates from the mid 1970s. The original compiler by Richards generated machine code for a generic stack machine, called OCODE, and the compiler itself was available in OCODE, so it was easy to port to new machines. Essex produced a version for the ICL 1900 to start with, and moved this across to TOPS-10 on their new PDP-10 in 1970. They then rewrote the compiler to remove the OCODE layer and improve performance.

This second implementation of BCPL is included in the TOPS-10 6.03 disk images we are using so there are no special set up instructions. The compilation system recognises files with a .BCP extension by default. Here's what a hello world program looks like and how to run it:

.type hello.bcp
GET "BCL:BCPLIB"
LET START() BE
$(
	WRITES(TTY,"Hello, world*C*L")
$)

.execute hello.bcp
BCPL:  HELLO	400031/2	32%
LINK:	Loading
[LNKXCT BCPL Execution]
Hello, world

EXIT

TPK in BCPL

Let's use the compiler to run the TPK algorithm. The source code can be found here. This can be loaded onto the disk using the techniques described in this article.

The TPK formula (√|x | + 5x³) is implemented as a function with a single expression:

LET TPK(X) = SQRT(#ABS X) #+ 5.0 #* X ** 3

As BCPL is typeless, different operators are needed for floating point and integer arithmetic such as #+ for floating point add. Note also that #ABS is a unary operator.

However, we have a problem in that SQRT is not part of the language library at this point in history. We can implement it using the Babylonian approximation:

LET SQRT(X) = VALOF
$(
    LET X1, X2 = X, X #/ 2.0
    WHILE #ABS (X1 #- X2) #> 0.0001 DO
    $(
        LET OLD = X2
        X2 := 0.5 #* (X1 #+ (X2 #/ X1))
        X1 := OLD
    $)
    RESULTIS X2
$)

This uses a VALOF expression which yields its results using RESULTIS. As there is more than one statement we use a block, marked by $( ... $). Semicolon can be used to separate statements, but is not needed if there is a single statement per line.

Inside the block we define a variable, LET OLD = X2, but note that assignment uses := ad plain = is used for comparison.

With this, we can supply the driving logic in START, BCPL's equivalent of C's main.

// Define constants
MANIFEST $( N = 11; IOVS = 300 $)

// Main program
LET START() BE
$(
    LET A = VEC N
    LET IOVECTOR = VEC IOVS
    INITIALISEIO(IOVECTOR, IOVS)
    WRITE(TTY, "Please enter :N numbers*C*L", N)
    FOR J = 0 TO N-1 DO A!J := RDF(TTY)
    WRITE(TTY, "Results are*C*L")
    FOR J = N-1 TO 0 BY -1 DO
    $(
        LET R = TPK(A!J)
        TEST R #> 400.0 THEN
            WRITE(TTY, "Too large*C*L")
        OR
            WRITE(TTY, ":F*C*L", R)
     $)
$)

BCPL allows both single line comments with // and block comments with /* ... */. C initially only took block comments, with single line comments first re-implemented by C++ and coming back to C in the 1999 standard.

MANIFEST sets up compile time constants, which we can use when declaring the stack vector LET A = VEC N. Elements of this vector can be accessed using infix !, eg A!2. Two FOR loops are used, one counting forwards to read in numbers and the second counting backwards to calculate and print results. BCPL has IF, but it only supports a single clause for the true case; TEST ... THEN ... OR supports both true and false cases.

I/O is done using RDF to read in a single floating point value and WRITE for output. I/O is stream based, and we use the predefined value TTY to communicate with the user's console. INITIALISEIO is needed before we use I/O to allocate buffers. WRITE supports output of different types using : as positional markers, eg :F for a floating point number. *C*L in the string means carriage return / line feed.

A full transcript of the program execution can be found here.

Further information

The Github PDP-10 organisation has the essex-bcpl repo which contains the source tape and manual for the version of the language. There is also MUD1 which is an early multi-user dungeon written in BCPL; this looks like it needs TOPS-10 7.03 and a KI CPU to run, however.

A completely separate implementation of BCPL on TENEX from BBN can also be found at tenex-bcpl.

The Computer History Museum Software Preservation Group has a detailed history of BCPL, including links to documents, papers and other implementations.

The classic reference to BCPL (and what I originally leaned the language from) is "BCPL - the language and its compiler" by Martin Richards and Colin Whitby-Strevens; there's a copy at the Internet Archive.

Martin Richards continues to work on BCPL and his home page has links to implementations for modern computers.

Questions, corrections, comments

I welcome any questions or comments, and also especially any corrections if I have got something wrong. Please email me at rupert@timereshared.com and I will add it here and update the main text.