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.


AID - the JOSS language for TOPS-10

AID, or the Algebraic Interpretive Dialogue, was DEC's first public version of the JOSS interpreter for the PDP-6/10.

JOSS - the JOHNNIAC Open Shop System - was an early time-shared language created at the RAND corporation in 1963. This ran on JOHNNIAC, a computer they had built in house, and offered 20-30 users who were not expert programmers the ability to write short mathematical programs.

By 1964 the JOHNNIAC was not powerful enough to support demand so RAND and DEC worked together on a successor, JOSS-II, to run on the new PDP-6. This was a complete operating system allowing multiple users to run JOSS from their own teletype.

AID is a standalone version of JOSS that runs as a normal program on TOPS-10. I'm not sure on how much of the code of JOSS-II is in AID, but DEC's manual says:

Digital Equipment Corporation is grateful to The RAND Corporation for permission to adapt the language processors of JOSS for the PDP-l0.

Installing AID

By default, AID does not come with the 6.03 version of TOPS-10 we are using, so we will need to restore it first from a tape of customer supported programs from pdp-10.trailing-edge.com.

  • Get a copy of this tape file and decompress it using bunzip or similar.
  • Copy it to your simh installation directory.
  • With TOPS-10 running, press Control-E on the simh console and attach the tape file to MTA0
sim> at mta0 bb-x130a-sb.tap
%SIM-INFO: MTA0: Tape Image 'bb-x130a-sb.tap' scanned as SIMH format
sim> continue
  • Log in to TOPS-10 as user [1,2], password FAILSA. Run BACKUP and restore the executable file to SYS:
.r backup

/tape mta0:
/rewind
/restore dskb:[1,4]=aid.exe
!
"Done

/exit
  • Finally, still as [1,2], set permissions:
.protect sys:aid.exe <155>

AID can now be run via R AID by any user.

Using AID

After you start AID, you can enter simple expressions at the * prompt.

.r aid
AID 20A(32) AT YOUR SERVICE ...

*type 22/7
	22/7 =	      3.14285714
*a = 12
*b = 30
*type a+b
	 a+b =	     42

or you can enter programs, using decimal line numbers:

*1.0 Do part 5 for i=1(1)5
*5.0 Type "Hello, world"
*do part 1
Hello, world
Hello, world
Hello, world
Hello, world
Hello, world

Note that loops can be added at the end of any expression: here we call part 5 (the hello world print) via for i=1(1)5 with the numbers being the initial, step and final value.

Just like the original JOSS, any errors you make will be reported with the single message "Eh?"

It's possible to save your code (and variables defined in the environment) to a disk file. First you specify a file number to work with:

*use file 123
Roger.

and then save your code to a item number under that file:

*file all parts as item 5
Done.

If you quit out of AID with a Control-C and do a DIR you can see the file that has been created - in this case QQ0123.J05. This is in binary format and so cannot be easily manipulated by editors such as SOS

To load in the code from disk:

*use file 123
Roger.
*recall item 5
Done.
*list all parts

1.0 Do part 5 for i=1(1)5
...

Running TPK

Let's use AID to run the TPK algorithm. The source code can be found here but will need to be typed in to the interpreter directly.

.r aid


AID 20A(32) AT YOUR SERVICE ...

*1.000 Let t(x) = sqrt(!x!) + 5 * x ^ 3.
*1.010 Type "Please enter 11 numbers".
*1.020 Do part 2 for j=1(1)11.
*1.030 Type "Results are".
*1.040 Do part 3 for j=11(-1)1.
*1.050 Stop.
*
*2.000 Demand a(j).
*
*3.000 Let r = t(a(j)).
*3.010 Type "Too large" if r > 400.
*3.020 Type r if r <= 400.
*
*do part 1
Please enter 11 numbers
	a(1) = *10
	a(2) = *-1
	a(3) = *1
	a(4) = *2
	a(5) = *3
	a(6) = *4
	a(7) = *4.3
	a(8) = *4.305
	a(9) = *4.303
       a(10) = *4.302
       a(11) = *4.301
Results are
	   r =	    399.8863
Too large
Too large
Too large
	   r =	    399.608644
	   r =	    322
	   r =	    136.732051
	   r =	     41.4142136
	   r =	      6
	   r =	     -4
Too large
Stopped by step 1.05.

This exposed a number of differences from the original JOSS:

  • Variables and functions are single characters, but are case sensitive.
  • The character set is different: use * for multiplication. ^ for exponentiation and !x! to find the absolute value of x.
  • There is no support for comments (which JOSS defines as lines starting with or ending with *.

Further information

On Bitsavers, DEC's documentation of the AID system can be found in the PDP-10 Timesharing Handbook from 1970: see the section "Conversational Programming With AID" starting on page 123.

RAND's documentation for JOSS, such as The JOSS Primer, can be used for AID with small modifications.

There's a reconstruction effort for the original JOSS-II on github. This includes source code, and also many other JOSS documents in the doc/ directory.

Later on, DEC created FOCAL which made more extensive changes to the JOSS language. This is available on the 6.03 distribution we are using (via R FOCAL) but I will look at this later on when I cover TOPS-20.

I've written about other JOSS dialects before: WIPL for MCP on the Burroughs B5500 and PIL for the Michigan Terminal System on the IBM System/360.

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.


TOPS-10: Algol 60

This week it's time to look at the Algol 60 compiler on TOPS-10. This was a supported product from DEC that integrates with the standard COMPILE/EXEC system. Files should have an extension of .ALG to be automatically recognised as Algol

As with every Algol compiler, there are several implementation dependent features to deal with issues like character set and for unspecified facilities like I/O.

Single pass compiler

The compiler needs only a single pass over the source code, which makes it fast, but does mean that functions used before they are defined are flagged as errors. A non-standard keyword FORWARD was added to work around this, so you can say FORWARD REAL PROCEDURE x; and then define x later in the program.

Source format and special characters

This version of Algol does not require keywords to be quoted (eg FOR vs 'FOR' ), but if you have programs already using quoted keywords you can add the /Q switch to the COMPILE command to accept this. Periods are allowed in identifiers to improve readability.

Some additional characters are used to represent language features which are implementation dependent

  • := for left arrow assignment
  • # for not equals to
  • ^ for exponentiation
  • @ or & for floating point exponents
  • ! for comments

There are two special forms for constants. Octal constants are introduced by a leading %, eg %770. ASCII constants - ie a single word of memory containing up to 5 ASCII characters - start with a $ and the constant surrounded by the user's choice of delimiter, for r example $/ABC/.

Strings

TOPS-10 Algol adds a string type, which is implemented as a pointer to a character array internally.

    STRING S, T;
    S := "HELLO WORLD";
    T := S;

Here S and T would point to the same byte array in memory. If instead the last line was T := COPY(S); then T would point to its own string.

Square brackets inside double quotes are used to incorporate special characters, for example "HELLO[N]" would print HELLO and a carriage return/new line.

Input/Output

I/O is not part of the Algol spec, so all this is implementation dependent. The basic output functions are WRITE for character data and PRINT for numeric data. READ is used to input data based on the type of parameter.

By default, I/O goes to the user's typewriter. You can select other places by using INPUT or OUTPUT to define a channel, eg INPUT(5, "CDR"); will define input channel 5 as going to the card reader. You then need to do SELECTINPUT(5) to direct all future input via READ to come from this channel. This facility also allows input and output for disk files.

Other language extensions

  • There is a LONG REAL type for additional floating point precision.
  • Procedures can be marked as EXTERNAL and linkage to other languages is allowed.
  • While loops, eg WHILE X < 10 DO X := X + 1;.
  • Variables can be declared as OWN, which has similar semantics to C's static variables, ie their lifetime continues when the block they are defined in is exited.

Running TPK

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.

.type tpk.alg
BEGIN
    COMMENT TPK ALGORITHM IN ALGOL 60;

    REAL PROCEDURE FN(X);
    VALUE X;
    REAL X;
    BEGIN
        FN := SQRT(ABS(X)) + 5*X^3
    END PROCEDURE;

    COMMENT Main program;
    INTEGER N, J;
    REAL ARRAY A[1:11];
    N := 11;
    COMMENT Read numbers;
    FOR J := 1 STEP 1 UNTIL N DO
        READ(A[J]);
    WRITE("RESULTS ARE[N]");
    FOR J := N STEP -1 UNTIL 1 DO
    BEGIN
        REAL RESULT;
        RESULT := FN(A[J]);
        IF RESULT > 400.0 THEN
            WRITE("TOO LARGE")
        ELSE
            PRINT(RESULT);
        WRITE("[N]");
    END LOOP;
END PROGRAM

.exec tpk.alg
ALGOL: TPK
12  NO DECLARATION SHOULD FOLLOW PROCEDURE OR SWITCH DECLARATION
LINK:	Loading
[LNKXCT TPK Execution]
10
-1
1
2
3
4
4.3
4.305
4.303
4.302
4.301
RESULTS ARE
 3.9988630&  2
TOO LARGE
TOO LARGE
TOO LARGE
 3.9960864&  2
 3.2200000&  2
 1.3673205&  2
 4.1414214&  1
 6.0000000
-4.0000000
TOO LARGE


End of execution.

Note the warning that the declaration of INTEGER N, J; should not follow the procedure declaration. This is not something that other Algol compilers have picked up and it may be due to the single pass nature of the compiler mentioned earlier.

In the output section, numbers are printed with the mantissa and exponent separated by a space, eg 3.9960864& 2 means 399.60864.

Further information

On Bitsavers there's an Algol manual from 1974. The tape image from 1978 that was the source of this compiler has a readme on trailing-edge.

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.


TOPS-10: The TECO editor

We looked at the SOS editor previously, now let's try the more powerful - and more complex - TECO (Text Editor and Corrector) on TOPS-10.

History

Dan Murphy wrote the first version of TECO for the PDP-1 in 1962 while working at MIT. The name originally stood for Tape Editor and Corrector - "tape" here meaning paper tape. For this machine programs were prepared offline using a teletype that could write a paper tape from the user's keystrokes. The tape was then taken to the PDP-1 and loaded. The problem was how do you make corrections to a continuous reel of paper tape? The solution Murphy came up with was to write a program that would take the user's source tape and a set of instructions punched to a second tape to edit it. It would apply the corrections in a batch and produce a new paper tape - so TECO was more like a language for processing text than what we would think of as an editor.

It became popular and the program was extended, adding interactive editing and many new commands. The program was ported to most of DEC's operating systems and other machines, and even became the underlying program for the first version of Emacs.

TECO concepts

Some important concepts to keep in mind when using TECO. Unlike the line-orientated SOS, TECO is character orientated: it keeps track of your location in the file via a pointer to a character position - or more precisely the position between two characters. This also means that TECO does not create line numbers and save them like SOS does.

TECO also uses the concept of pages in a file. Each page can be arbitrary length and the separator between pages is a form feed (or Control-L). When TECO starts, it will read in the first page and operate on that; you can then use commands to move on to the next page. This feature allows files that would be larger than core memory to be edited. In practice, if you are creating or editing your own files you will probably not use this feature, but it's something to watch out for if editing older files.

Getting in and out

There are two ways to start TECO: MAKE file.ext will create a new file file.ext and start editing it, and TECO file.ext will open an existing file and position you at the top of the first page. TECO will create a backup file file.bak containing the version of the file before you made edits. Whichever way you invoke it, TECO will print the * prompt showing it is ready to accept commands.

The EX command will save and exit; pressing Control-C will abandon your edits and return you to the command line.

Commands

TECO commands are single or two letter combinations, with an optional numerical prefix and a textual suffix. Textual suffixes must be terminated by pressing Altmode (ie Escape on your keyboard, which will print a $). Several commands can be entered as one long stream. To tell TECO that your command is ready to process, press Altmode twice; just pressing Return will be treated as a textual suffix and will not submit the command.

An example: the C command moves forwards by one character. With a prefix it moves by that number of characters, eg 4C moves forwards 4 characters and -2C moves back 2.

Other useful navigation commands are L to move by line (with 0L meaning move to start of line), J to move to start of buffer and ZJ to end of buffer.

The S command searches for text and if found, moves the character position. The prefix determines which match to jump to and the suffix what text to look for, so 2Shello would jump to the second instance of hello in the buffer.

TECO will not print any of your file unless you tell it to. T will print from the character position to the end of line, and takes prefixes if you want to print more lines. HT prints the whole page.

I will insert the text in the suffix to the current character position, including any whitespace like Return. The Tab command works the same way but will insert an actual tab followed by the text in the suffix.

D will delete the number of characters supplied in the prefix; K will delete by lines.

A sample session

Let's take as an example our hello world program.

        DO 1 J=1,5
1       WRITE(5,2)
2       FORMAT(' HELLO, WORLD')
        END

We want to change 'WORLD' to "EARTH'. The session could look like this:

.teco hworld.for

*sWORLD$$
*-5d$$
*iEARTH$$
*0lt$$
2	FORMAT(' HELLO, EARTH')
*ex$$
.

After invoking TECO we search for the first instance of 'WORLD'. We then delete the preceding five characters (as S places us after the search match) and use I to insert the replacement text. We then use 0L to move us to the start of the line and T to type out the line to confirm the change looks OK. We then use EX to save and exit.

If we were feeling confident, we could do this whole change on a single line:

.teco hworld.for

sWORLD$-5diEARTH$ex$$

.

There's also a find and replace command that can do the above: FSold$new$ will replace 'old' with 'new'

Advanced usage

This only scratches the surface of what TECO can do. Some of its other features are to automatically type out where you are when a search is executed. "Q-registers" where you can store and retrieve sections of text, and iteration and branching - so TECO is Turing complete. The Programmer's Reference Manual weighs in at 144 pages and describes each command fully.

Summary of commands discussed

Command Meaning
C Move by character
D Delete characters
EX Save and exit
F Find and replace
I Insert the text in the suffix
J Move to start of buffer
ZJ Move to end of buffer
K Delete lines
L Move by line (takes prefix)
0L Move to start of line
R Move back by character
S Search by suffix text
Tab Like I, but inserts a tab first
T Type out text
HT Type out the whole file

Further information

On Bitsavers, the 1975 Introduction to TECO is a good place to start. The TECO Programmer's Reference Manual gives a complete description of each TECO command.

Dan Murphy, the original author of TECO, describes the origin and initial development of the editor in The Beginnings of TECO.

There's a version of TECO for modern computers on Github.

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.


TOPS-10: Fortran

Today we'll take a quick look at programming using Fortran on TOPS-10.

Compiler versions

As noted in the programming introduction , there were two Fortran compilers for TOPS-10: the original compiler, later called F40, that supported Fortran 66, and Fortran-10 which was introduced in around 1972 that supported Fortran 66 and, in later versions, Fortran 77. The 6.03 disk images we have contains both, under SYS:F40.EXE and SYS:FORTRA.EXE respectively. We will look at Fortran-10 in this article.

By default, files with an extension of either .F40 or .FOR will be compiled by Fortran-10: to force the use of F40 you need to provide the /F40 compile-time switch eg COMPILE/F40 HWORLD.F4.

Implementation features

The standard Fortran-66 fixed format is used (labels in columns 1-5, comment/continuation in col 6, program text cols 7-72) but is relaxed slightly: a line starting with a tab is assumed to skip over the initial label.

There are a number of extensions to standard Fortran 66 including:

  • Multiple statements can be combined on a single line using ;.
  • Comments can be added anywhere in the line by starting them with !.
  • Octal constants allowed, starting with a ".
  • Literal strings, eg "YES" are allowed along with Hollerith literals (eg 3HYES).
  • Two-way IF statements for logical comparisons, eg IF B THEN 100,200 will go to statement label 100 if B is true, else 200.
  • PAUSE allows you turn on trace mode, which prints subroutine calls.

There is also extensive I/O capabilities via a package called FOROTS. Random access, and append to files is supported, as well as utilisation of TOPS-10 devices.

The 1974 manual describes a source level called FORDDT, but this does not seem to be available on the version of TOPS-10 we have.

Running TPK

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.

Type EXEC TPK to compile and run the program. To get a listing of the program you can add /LIST to the EXEC or COMPILE command. It will create a temporary listing file and send it to the printer when you log out.

While developing this, I found I had to change both the WRITE and READ unit numbers to be 5.

The compiler also uncovered a mistake I had made in the original version of the program. Before I had the function definition followed the DIMENSION for the array:

      FTPK(X) = SQRT(ABS(X)) + 5.0*X**3
      DIMENSION A(11)

but I got a warning

00004	      DIMENSION A(11)
%FTNSOD LINE:00004  DIMENSION STATEMENT OUT OF ORDER

The fix for this is to switch the order of the two lines. After that, it ran as expected.

Here's the compile and execution looks:

.type tpk.for
C     TPK ALGORITH IN FORTRAN 66
      DIMENSION A(11)
      FTPK(X) = SQRT(ABS(X)) + 5.0*X**3
C     MAIN PROGRAM
      N=11
      WRITE(5, 100)
 100  FORMAT(24H PLEASE ENTER 11 NUMBERS)
      READ(5, 101) A
 101  FORMAT(F9.4)
      WRITE(5, 102)
 102  FORMAT(12H RESULTS ARE)
      DO 3 J = 1, N
      K = N - J + 1
      RESULT = FTPK(A(K))
      IF (RESULT .LE. 400) GOTO 2
 1    WRITE(5, 103)
 103  FORMAT(10H TOO LARGE)
      GOTO 3
 2    WRITE(5, 101) RESULT
 3    CONTINUE
      STOP
      END

.exec tpk.for
FORTRAN: TPK
MAIN.
LINK:	Loading
[LNKXCT TPK Execution]

PLEASE ENTER 11 NUMBERS
10.
-1.
1.
2.
3.
4.
4.3
4.305
4.303
4.302
4.301

RESULTS ARE
399.8863
TOO LARGE
TOO LARGE
TOO LARGE
399.6086
322.0000
136.7320
 41.4142
  6.0000
 -4.0000
TOO LARGE
STOP

END OF EXECUTION
CPU TIME: 0.00	ELAPSED TIME: 1:19.92
EXIT

.

Further information

On Bitsavers, the FORTRAN-10 Language Manual from 1974 is the closest reference to the version of Fortran-10 that we have on TOPS-10 6.03. There's also a guide to F40 in the FORTRAN IV Programming Manual from 1969.

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.


Next →