TOPS-10: BASIC

BASIC on TOPS-10 is very similar to the version of Dartmouth BASIC from 1968. It runs in its own environment where you can edit programs and load/save file, and is interpreted rather than compiling source to machine code first.

Language features

As in 4th edition Dartmouth BASIC, the DEC version supports

  • LET (optional) to define variables, DIMENSION to declare arrays.
  • PRINT, INPUT for I/O
  • READ, DATA to encapsulate data in a program
  • IFTHEN for conditionals
  • FOR .. NEXT for loops
  • GOTO, ONGOTO, GOSUB & RETURN for transfer of control.
  • DEF FN for single line functions

END is needed as the last statement of the program.

The default variable type is floating point. String variables are available by suffixing the name with a $. Both types can be formed into arrays using DIMENSION. There is no integer type, but there is a matrix capability via the MAT keyword.

It also has these common extensions to Dartmouth 4th edition

  • STOP will half the program
  • REM for comments
  • CHAIN to load in a new program from the existing program
  • Page and margin control
  • String concatenation via +, string/number conversion via ASC/CHR
  • Character access via LEFT$, MID$ etc.
  • Access to disk files via FILE.

Controlling the interpreter

Start BASIC with R BASIC. Lines starting with a number are taken as part of the program. You can correct a line by typing it again and delete a line by just typing its line numbers.

Lines not starting with a number are commands.

Command Meaning
BYE Logs out of TOPS-10
CATALOG List files on disk
DELETE range Delete multiple lines
HELP Prints the help file
KEY / TAPE Switch between keyboard and paper tape
LIST [range] List program with optional line number range
MONITOR Returns to TOPS-10
NEW [filename] Start a new program with optional filename
OLD filename Read in an existing program from disk
QUEUE filename Print filename to printer
RENAME filename Change name that program in core will be saved to
REPLACE [filename] Replace filename with program in core
RESEQUENCE Renumber the program
RUN Execute program
SAVE [filename] Save program in core to filename
SCRATCH Remove the program from core
SYSTEM Returns to TOPS-10, erasing core
UNSAVE filename Deletes filename
WEAVE filename Merges the file with the current one in core

Files are not saved to disk unless you type SAVE. Both MONITOR and SYSTEM will return you to TOPS-10, the difference being that MONITOR will preserve core (see how jobs work) and SYSTEM does not.

Running TPK

As a demonstration, we'll run the TPK algorithm. in BASIC. This uses the 4th edition version unchanged. I created the file on my PC and loaded it into my TOPS-10 account using the techniques described here. Then when I start BASIC I use the old command to read this from disk.

.r basic


READY, FOR HELP TYPE HELP.
old tpk

READY
list


TPK           15:36         07-MAR-79



100 REM TPK ALGORITHM IN BASIC
110 REM
120 DEF FNT(X) = SQR(ABS(X)) + 5*X**3
130 REM
140 REM MAIN PROGRAM
150 DIM A(11)
160 LET N=11
170 PRINT "PLEASE ENTER", N, "NUMBERS"
180 FOR I = 1 TO N
190 INPUT A(I)
200 NEXT I
210 PRINT "RESULTS ARE"
220 FOR J = 1 TO N
230 LET K = N - J + 1
240 LET R = FNT(A(K))
250 IF R > 400 THEN 280
260 PRINT R
270 GOTO 290
280 PRINT "TOO LARGE"
290 NEXT J
300 END

READY
run

TPK           15:36         06-MAR-79



PLEASE ENTER   11           NUMBERS
 ?10
 ?-1
 ?1
 ?2
 ?3
 ?4
 ?4.3
 ?4.305
 ?4.303
 ?4.302
 ?4.301
RESULTS ARE
 399.886
TOO LARGE
TOO LARGE
TOO LARGE
 399.609
 322
 136.732
 41.4142
 6
-4
TOO LARGE



TIME:  0.00 SECS.

READY

system

EXIT

.

Note that the line numbers in BASIC will not be recognised by TOPS-10 native commands as they expect 5 digit line numbers. However, the SOS editor has a /BASIC switch that does understand these so can be used to edit BASIC code outside of the interpreter.

BASIC programs on the DECUS tapes

There are a number of user contributed BASIC programs on the DECUS tapes (which were discussed briefly in the last article). Component 72 is a snapshot of the Dartmouth program library. Component 97 is a set of lessons for BASIC written in BASIC itself. Component 103 contains some mathematical routines in BASIC and Fortran.

Let's restore and run the teaching program in component 97 as an example. Looking at the trailing-edge page, component 97 will be on tape DECUS 10-LIB-1:

             DECUS 10-LIB-1                  5.64 Mbyte      902
      Contains 10-3 through 10-138           compressed   extracted
             except 10-101                   tape image     files

If you click on the extracted files link next to this you will see all files on the tape. Search on the page for 97.inf and you will see below that a set of BASIC files.

   1       25(7)  <007> 43,50014 31-Mar-75 dcus:[43,50141]97.inf
   12     1467(36) <007> 43,50014  9-Oct-70 dcus:[43,50141]tutr01.bas
    9     1099(36) <007> 43,50014  9-Oct-70 dcus:[43,50141]tutr02.bas
   11     1311(36) <007> 43,50014  9-Oct-70 dcus:[43,50141]tutr03.bas
   10     1202(36) <007> 43,50014  9-Oct-70 dcus:[43,50141]tutr04.bas
   11     1379(36) <007> 43,50014  9-Oct-70 dcus:[43,50141]tutr05.bas

Take a note of the user ID for this component - [43,50141].

On the previous page, download a copy of the compressed tape image and decompress it with bzip2 -d decuslib10-01.tap.bz2. Copy this under your simh directory, press Control-E on the console and attach the tape:

sim> at mta0 decuslib10-01.tap
%SIM-INFO: MTA0: Tape Image 'decuslib10-01.tap' scanned as SIMH format
sim> c

Login as the operator (user 1,2 password failsa) and restore the files using BACKUP. Here I'm going to put all the files in my home directory for user [200,200] but they can be placed anywhere you want.

.login 1,2
JOB 11 KA603 TTY1
Password: 
[LGNJSP Other jobs same PPN]
1100	22-Mar-79	Thur

.r backup
/tape mta0:
/restore dskb:[200,200]=dcus:[43,50141]*.*
!43,50141	DCUS

"Done

/^C

Note it will take about a minute to restore the files as the tape needs to be read sequentially.

Now switch over to your user account and run the newly restored program:

.r basic


READY, FOR HELP TYPE HELP.
old tutr01

READY
run

TUTR01        11:02         22-MAR-79



WELCOME TO TIMESHARING PDP-10.WE WILL
TRY TO TEACH YOU ENOUGH ABOUT THE SYSTEM IN THIS SITTING SO THAT
YOU WILL BE ABLE TO WRITE YOUR OWN COMPUTER PROGRAMS.

Further information

On Bitsavers, the 1974 BASIC Conversational Language Manual is probably the best guide for this version of BASIC. There's also 1968's Advanced BASIC for the PDP-10 from which the 1974 version looks to have been derived.

Both manuals draw material from Dartmouth's documentation (see their manual for a comparison). Interestingly, the 1968 DEC manual notes Dartmouth as the registered trademark holder of BASIC, and thanks them for using the material in their manual. The 1974 manual has no mention of BASIC being registered nor any acknowledgements to Dartmouth.

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: Programming

A large number of programming languages were available for TOPS-10, both provided by DEC and created by third parties. In this article we'll take a quick tour of what is available today, along with a brief look at the TOPS-10 programming environment.

Programming languages available

From DEC

DEC provided the MACRO-10 assembler, and compilers for the big three languages of the time: Fortran, Algol and COBOL. DEC also had its own system programming language called BLISS.

There were two main versions of Fortran: F40, used until around 1972 and supporting Fortran IV/66, and Fortran-10, which took over after then and supported Fortran 66 and 77. The COBOL version is COBOL-68 and Algol is Algol-60.

On the interpreter side, DEC produced a BASIC and several languages inspired by JOSS - the initial one was AID, which was similar to RAND's JOSS II, and this was supplanted by Focal, DEC's own dialect that was implemented on its other machines.

DEC also produced an APL interpreter called APL-SF, but note this did not run on the KA CPU.

Versions of all the above exist on the disk images we are using.

From third parties

A probably incomplete list of preserved languages that can run on TOPS-10 is:

  • Algol-W - Niklaus Wirth's follow-up to Algol 60
  • BCPL - a compiled language that was one of the forerunners of C.
  • ECL - an interactive programming language from Harvard
  • Forth - a stack based language
  • IMP72 - a system language
  • Edinburgh IMP - a completely unrelated Algol like language
  • Lisp - several varieties of the list processing language
  • Logo - a language for teaching programming
  • Pascal - Wirth's follow-up to Algol-W
  • PILOT - a computer aided instruction language
  • SAIL - Stanform's extended Algol 60
  • SAM-76, a functional text processing language like TRAC
  • Simula - an early object orientated language
  • SNOBOL, a text processing language

Sources

Many of these were preserved via the DECUS tapes - a collection of software donated by PDP-10 users that DEC would distribute to users via tape on request. The program catalogue from 1978 can be found on Bitsavers and the tapes on trailing-edge.com. Software can be loaded via the TOPS-10 BACKUP utility as described in a previous post.

Others have been collected at the Github PDP-10 repo, often together with documentation.

Coverage on this blog

For this section of the blog, I will in later posts cover languages available on the TOPS-10 version we are using (6.03) and processor (KA). Discussion of Lisp will be deferred to future ITS/WAITS articles, and most post 1975 languages will be covered when I eventually look at TOPS-20.

The TOPS-10 compilation system

TOPS-10 has an common program development system for compiled languages where you can type EXECUTE src where src is a source file like HWORLD.FOR and it will work out what to do to execute the program. The steps it takes behind the scenes are

  • If the object file is newer than the source file, skip to the next step. Otherwise it will run COMPILE on the source file which works out what compiler to use based on the file extension and invokes it, This produces an object file with a REL extension.
  • Runs LOAD src.REL which loads the object file into memory and resolves external references - similar to linking on modern systems, but the result is now in core memory, not saved to an executable file.
  • Starts the program now in core memory using START.

If you want to create an executable file, you can use LOAD on the .REL file and then type SAVE name. This will create name.SAV which you can then invoke by typing RUN name.

As an extra convenience, the monitor will remember the parameters for the last 'compile-class' command. So if you type COMPILE hworld you can then just type LOAD and SAVE and it will use hworld as the parameter automatically.

There are several operators that can be used for more complex compile scenarios. COMPILE a+b+c will concatenate the three files a, b and c to produce a single output. COMPILE bin=src will create bin.REL after compiling the file identified by src. Compile options can also be saved into config files and then referenced in the command line by preceding them with @. See section 1.5 of the Operating System Command Manual linked below for more details.

Programmer's tools

As well as compilers, there are a number of tools helpful for programmers included in TOPS-10.

  • CREF produces a cross reference listing of symbols
  • MAKLIB to create object libraries
  • DDT for debugging

Further information

See the Operating Systems Command Manual on Bitsavers for full documentation of the compile-class command.

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 utilities: PIP, RUNOFF, FILCOM and SEND

This week we'll look at four utilities included in TOPS-10: PIP, for file copy and translation; RUNOFF to produce text documents; FILCOM to compare files; and finally the chat facility provided by SEND.

PIP

PIP or the Peripheral Interchange Program, is a tool to copy and transform files that was implemented on most of DEC's operating systems, and later copied by CP/M. Many of the file handling utilities on TOPS-10 such as COPY or RENAME are actually implemented by calling PIP.

Start the program with R PIP and then give commands at the * prompt. Note you can't supply arguments on the command line directly. Once at PIP's * prompt, the command format is

dest = src /flags

You can enter multiple commands, and can then press Control-C to exit when done.

Wildcards are allowed, eg to make a backup of all your Fortran files under the extension .BAK you could do *.bak=*.for. Device names, like TTY: for the terminal, or a tape drive logical device, can be used as well.

PIP is a bit of a Swiss Army knife of a tool: there are 25 flags that can be used in this version, consuming all letters from A-Z except K. Many of these are only useful when dealing with specific hardware such as tapes. Here are some of the other ones of interest when running TOPS-10 under emulation.

Flag Meaning
/C Delete trailing spaces and convert multiple spaces to tab
/J Convert non printing character to printable, eg ^A
/N Delete sequence numbers
/O Adds sequence numbers
/Q Prints out help options
/W Converts tab to spaces

RUNOFF

RUNOFF is a document production system that takes a text file that has been marked up with special codes and produces an output text file that is suitable for printing. The concept is clearly inspired by CTSS's RUNOFF, with typesetting codes indicated by a period as the first character of the line, and many similar commands. For example, both CTSS and TOPS-10 use .center to indicated that the next line should be centred on the page. The TOPS-10 program does have more commands, including support for footnotes, indices and callouts. Special flag characters can precede text for additional effects: & will underline it while > will add the next word to the index.

To use, prepare your input file using a standard text editor like SOS and give it a .RNO extension. Start RUNOFF with R RUNOFF and give the input file at the * prompt. It will report any errors and write an output file with the extension MEM.

Here's an example of RUNOFF processing this section's text:

.type blog.rno
.chapter RUNOFF
&R&U&N&O&F&F is a document production system that takes a
text file that has been marked up with special codes
and produces an output text file that is suitable for printing.
.note
Be sure to read the manual first
.end note

.r runoff

*blog
BLOG	1 page
*^C

.type blog.mem

			 CHAPTER 1

			   RUNOFF



RUNOFF is a document production system	that  takes  a	text
______
file that has been marked up with special codes and produces
an output text file that is suitable for printing.


			    NOTE

	       Be sure	to  read  the  manual
	       first

FILCOM

FILCOM is a utility to determine differences between files, similar to diff on Unix. Start it with R FILCOM and at its * prompt give commands of the format

output = input1,input2

where input1 and input2 are the files to compare and output is the output file - which can be omitted, in which case the differences are displayed on the terminal.

For an example we'll use our Hello World program from before and just change one line so it prints Hello Earth instead, and save that to a file hearth.for. Here's what FILCOM prints when comparing the two files:

 .r filcom

 *=hworld.for,hearth.for
 File 1)	DSKB:HWORLD.FOR[100,100]	created: 1441 16-JAN-1979
 File 2)	DSKB:HEARTH.FOR[100,100]	created: 1100 21-FEB-1979

 1)1	00400	2	FORMAT(' HELLO, WORLD')
 1)	00500		END
 ****
 2)1	00400	2	FORMAT(' HELLO, EARTH')
 2)	00500		END
 **************

 %files are different

The lines that differ are marked with n)m where n is the file number and m the page in the file. This is followed by a line where both files are the same (the END statement here) for identification purposes.

By adding the /U switch it will print differing lines with a bar in column 1 like the below. But there is no equivalent of Unix's < and > to show both lines side by side.

|	00400	2	FORMAT(' HELLO, EARTH')
|	00500		END

FILCOM will also compare binary files, for example looking at two object code files:

*=hworld.rel,hearth.rel
File 1)	DSKB:HWORLD.REL[100,100]	created: 1443 16-JAN-1979
File 2)	DSKB:HEARTH.REL[100,100]	created: 1101 21-FEB-1979

000024	536372 246210	426032 252220	110340 014030

%files are different

Here the output is broken into three fields: the octal address, file 1's word, file 2's word and the XOR of the two words.

By default FILCOM will decide whether to do a text or binary comparison by looking at the file extensions. You can override this by giving a flag after the input file specifications: /A to force ASCII text mode, /B to force binary mode.

Some other useful flags are /S to ignore spaces and tabs, and /C to ignore comments (text after ;).

SEND

SEND allows one-way communication from one terminal to another. The parameters can either be a terminal device

.send dev:message

or a job number

.send job message

An example of the former would be if you are logged in on TTY0: and want to send a message to TTY1:

.send tty1:hello

On tty1 the bell will ring and it will type

;;TTY0: - hello

You can also specify cty: as the device to send the message to the operator's console.

More information

The User Utilities manual on Bitsavers describes the PIP, RUNOFF and FILCOM commands in more detail, but note this manual is for a later version of TOPS-10. The Operating Systems Command Manual documents the SEND command.

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: Users

Today let's investigate how users and logins are handled on TOPS-10. We will see how to look at user information and add new ones, and there's also a brief discussion of the security of this version.

Viewing user information

The REACT program is used to manage users on TOPS-10. Although you can start this program as a regular user, it will not have much effect as it cannot read the accounting files in SYS:. So first, login as the operator, which has a user ID of 1,2 and password failsa, and then start REACT.

.login 1,2
JOB 11 KA603 TTY1
Password: 
[LGNJSP Other jobs same PPN]
1018	22-Feb-79	Thu

.r react

FOR HELP TYPE "H<CAR RET>"

*

Type r to read in the main accounting file SYS:ACCT.SYS and t to display it on the terminal. For the second command, it will ask you if you want to list codes (ie passwords) as well.

*r
[8 OUT OF 500 ENTRIES ARE USED]

*t
LIST CODES?y
...
100	100	DEMONSTRATIO	000000000000	DEMO1 	777777777777
	511,,511 2,,5      	017500001763		      
	8 SEP 768	0	511

There's no ready source of documentation for these fields, but you can work out what some of them are by context, such as the account name and password.

To view the other accounting file SYS:AUXACC.SYS type a. REACT will read this file into its buffer and you can again display it with t.

*a
[5 OUT OF 500 ENTRIES ARE USED]

*t

...

100	100	DSKB  	0	100000	100000	0

This indicates what disk structures the user is allowed to access.

Type e to exit REACT.

Creating a new user

Often you will want to set up a new user ID beyond the 100,100 account so you can arrange your work or just to have a personalised account. To prepare for this, first choose an octal project and programmer ID pair. For regular users these numbers should be larger than 10. For this example let's choose 200,200. Also select a login name (here rupert) and a password (here secret). Passwords can be 1-6 characters long and are case-insensitive.

The easiest way to do this is copy an existing user and edit it.

Logged in as user 1,2 again, start REACT and load the main accounting file with r. Type i 200,200=100,100 to copy the user from 100,100 and then c 200,200 to change the name and code fields. Finally, write the file with w.

*i 200,200=100,100

*c 200,200
CHANGE: name
ARGS: rupert
CHANGE: code
ARGS: secret
DEMO1  WAS THE OLD CODE FOR THAT NUMBER
CHANGE: 

*w

Next, change the auxiliary accounting file to give access to disk structures. Load it in with a, insert a copy with i, save the file and exit.

*a
[5 OUT OF 500 ENTRIES ARE USED]

*i 200,200=100,100

*w

*e

EXIT

One last task needs to be done - create a user file directory for the new login. Still as 1,2, use the credir command.

.r credir

Create directory: [200,200]
  Created DSKB0:[200,200].UFD/PROTECTION:775
Create directory: ^C

The login is now ready for use.

Changing passwords

Using REACT you can change the password for any user with the C command, choosing the code option as shown above.

It should also be possible for a user to change their own password. The help file for LOGIN indicates this is done by giving the /PASSWORD switch at login time, ie

.login 200,200/password

However, the version we have does not seem to support this.

Who am I?

With multiple logins it can be easy to lose track as to what you are currently logged in as. The PJOB command will help here.

.pjob
Job 11   User RUPERT   [200,200]   TTY1

Predefined users

Certain user IDs have a predefined function. There is often also a logical device name associated with these, so for a directory of system help files you can do either dir [2,5] or dir HLP:.

User Meaning Logical
1,1 Master File Directory MFD:
1,2 System operator
1,3 Old versions of programs OLD:
1,4 System programs SYS:
2,5 Help files HLP:
3,3 Batch queues
5,11 Relocatable object library REL:
5,17 MACRO universal files UNV:
10,6 Software distributions
10,7 Software distributions DEC:

The full set of defined users - including those empty or not present on the disk files we are using - can be found in the Operating Systems Command Manual Appendix A.

Security

As you can see from the above, security is fairly rudimentary on this version of TOPS-10. Passwords can be as short as one character, are stored in plain text and being case-insensitive their strength is not great.

At login time, if you enter an invalid user id or an invalid password for an existing user id you do get the same message.

?LGNIET INVALID ENTRY - Try again

so it is not possible to guess user IDs this way. However, some minitor commands such as SYSTAT are available before you login, so you can get a list of currently logged in users that way. It is possible to keep trying different passwords - there is no lock out after a number of attempts - but the system introduces a small delay after each attempt which at least slows this down.

More information

For TOPS-10 6.03 there is no printed documentation for REACT available that I can find. There is a short help file available via H while running REACT, or via HELP REACT from the command line.

Bitsavers has the Monitor Installation Guide with a description of REACT for TOPS-10 version 7, but this is quite different from the version we have in 6.03.

The Operating Systems Command Manual documents the LOGIN command, but note this differs slightly from what the online help file for LOGIN.

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.


Reconstructing OPL: Joseph Weizenbaum's Online Programming Language

Joseph Weizenbaum joined MIT in 1963 and is most famous for creating ELIZA, the world's first chatbot. One of his other projects was OPL, the Online Programming Language, which used his SLIP list processing library to implement an interpreted, interactive language that could take advantage of the new facilities provided by time-sharing operating systems like CTSS. First conceived in 1963, it was a contemporary of such languages as JOSS, BASIC and interpreted LISP. It was even combined with a later version of ELIZA to provide probably the first embedded scripting language.

Based on a printout of source code found among Weizenbaum's papers archived by MIT Libraries, I have reconstructed the language so it can live again for the first time in nearly sixty years on a IBM 7094 emulator running CTSS. I'm calling this a reconstruction as although the main logic of the interpreter was in the printout, the I/O routines and some utility functions were missing. I have written these in MAD (Michigan Algorithm Decoder), which is the source language of OPL. Around 18% of the project is new code.

Source code and further details on Github.

/images/ctss/opl.png OPL printout and code execution under emulation. Source: MIT Libraries and Rupert Lane. License: MIT

The origins of OPL

The first reference to OPL is in OPL-I An Open Ended Programming System Within CTSS. a Project MAC technical report written by Weizenbaum in April 1964. He notes the shared observation that new time-sharing operating systems like CTSS allow programmers to attack problems in new ways that traditional high-level languages like Fortran do not allow.

The whole point of time-sharing is to enlarge the opportunities for carrying out truly significant man-machine dialogue — not to merely reduce turn-around-time.

with a longer term goal

It is to be expected that in many problem areas the computer will begin to help man by doing only the most obvious in mechanical parts of his problem but that, as the man-machine dialogue extends over a long period of time, more and more of the previously fussy issues over which man retained authority will become clear and finally be turned over to the computer.

(which rings true today with the advent of LLM based coding agents).

But Weizenbaum does draw the line somewhere - "The goal is to give to the computer those tasks which it can best do and leave to man that which requires (or seems to require) his judgement." - the fundamental difference between humanity and machines would be something he increasingly feel strongly about as he became more disillusioned with AI in the 1970s and wrote his book Computer Power and Human Reason

But turning to the details on the language itself. OPL-I, as it was called then,

permits the user to augment both his program and his data base during widely separated successive sessions at his terminal. Facilities are provided which make it possible for the user to operate on his already established data base both by means of built-in operators and in terms of operators (functions) which the user has previously defined in the language of the system.

The OPL system is written in a high level language and uses the SLIP list processing library for its internal workings; the same SLIP functions are exposed to the user for their programs. Users can build up their own lists and functions that operate on them, and can run small fragments of code to interrogate their data. These queries are discarded after running them, but the data and functions remain.

Persistence over sessions is afforded by a facility in CTSS which is not really available on modern operating systems. At any time while running OPL, the user can press the Interrupt key on their typewriter console and it will return them to the CTSS command level. The user can then type SAVE and a complete record of their core memory - the OPL program and the user functions and data - is stored to a disk file. At any point later they can RESTORE that core image file and pick up where they were before they pressed Interrupt, without the need to load in data and code again.

As a concrete example, Weizenbaum gives code for a function that calculates the mean of a list of numbers.

(DEFINE)
(MEAN (L)
          (S = SEQRDR(L))  (SUM = 0.0)  ((COUNT = 0.0)
BEGIN     (C = SEQLR(S,F))
          IF(F)MORE,MORE,DONE
MORE      (COUNT = (COUNT + 1.0))
          (SUM = (SUM + C))  GOTO BEGIN
DONE      ( (SUM/COUNT))  )

Here, the SLIP primitive SEQRDR is used to start an iterator over the list L. Each call to SEQLR brings in the next element, travelling left to right over the list's contents. The flag F denotes what has been read: this will be 1 at the end of the list, so the Fortran II style IF statement is used to branch to the label DONE at this point, where the mean is returned. This could be called by something like:

        (X = MEAN(SET))

Weizenbaum gives an example of a use for OPL. An organisation can define its management hierarchy as a set of lists arranged into a tree, and write functions to do tasks such as compute the budget at an arbitrary level based on money allocated to sub-organisations, "What-if" scenarios could be run by temporarily changing the data.

He also looks ahead to a potential multi-terminal version of OPL (which as far as I can tell was never built) where several people could play a business game, sharing the same data and seeing the impact of changes performed by one player on the other terminals.

Weizenbaum compares OPL to LISP and IPL-V, and states "OPL-I is of a character quite similar to the LISP program mode and of about equivalent power." IPL-V was falling out of use at this time, and LISP was growing its number of users. The AI lab at MIT was using LISP on the PDP-1 and PDP-6, and I would speculate that there was an interchange of ideas between the groups.

The marriage of OPL and ELIZA

The next sighting of OPL is a talk Weizenbaum gave at UCLA in the spring of 1965, reproduced in a 1966 paper On-Line User Languages. This does not add much technical detail, but Weizenbaum puts OPL in the context of wider interpreter development at MIT. He views the command line environment in CTSS as an ad hoc interpreter to solve problems, and mentions other CTSS interpreters such as COGO.

But the really interesting news came in the Project MAC progress report for 1966-7. Weizenbaum had turned OPL into an evaluator that could be called by ELIZA to do arbitrary computation:

The difficulty with the early ELIZA system alluded to above was that the system could do no computation in any significant sense. […] In order to enhance the ability of ELIZA in this direction it was necessary to design and build an evaluator, that is, an interpretive program, to which computational and logical tasks could be given for execution. Such an evaluator was built.

The composition of this evaluator proved to be an interesting task with ramifications extending beyond its immediate utility as a subsystem to ELIZA. An effort was made to design the program in a way such that it would prove a useful tool for the teaching of a number of deep issues in the field of programming languages. The resulting program is essentially an interpreter version of SLIP with approximately the power of LISP but with very considerably simpler syntactic conventions than LISP.

Note the source code for this combination of ELIZA and OPL has not been found. What we do have is the stand alone version of OPL from 1967.

But there are several documents that show ELIZA+OPL being used. Paul Hayward's 1967 bachelor's thesis Flexible Discussion Under Student Control in the ELIZA Computer Program describes creating a system to have a computer interactively discuss a physics problem with a student, and gives his scripts at the end of the document. Two papers by Edwin Taylor from the Science Teaching Group at MIT: The ELIZA Program: Conversational Tutorial and Automated Tutoring and Its Discontents describe the experience of building teaching scripts using ELIZA+OPL.

Of most interest is Hayward's 1968 Eliza Scriptwriter's Manual which contains a description of how ELIZA and OPL work together, including an appendix on the OPL language and its functions. This is the sole documentation we have on OPL.

The source code

The printout of source code we have is for a standalone version of OPL without its ELIZA embeddings. The document contains

  • Three pages of what looks like a larger manuscript about OPL, describing how it uses description lists to store symbol tables.
  • A listing of the file DI MAD. This is the main entry point for the stand alone program. It accepts commands from the user's terminal and sends them to EVAL for evaluation. It has some debug facilities the user can turn on or off, such as printing how much free space is left.
  • A listing for the file EVAL MAD. This is an external function that takes a list expression as input and returns the result of its evaluation. It contains a parser and run time support for OPL's statements.
  • A listing of a CTSS ARCHIV file called FAP FAP that contains several FAP assembly language files that support OPL. An example of these is OBEY FAP which has a jump table that matches OPL function names with their entry points. This had to be done in assembly language as MAD lacks function pointers.

In total there are 48 pages or 2654 lines of code in the printout.

The printout is dated "06/30" and I think the year is 1967 based on the internal dates on the files in the FAP FAP archive. The user ID printed on top of the printout is T0109 2531 which from other printouts we know is Weizenbaum's.

Having studied his 1965 ELIZA code for that reconstruction, it's instructive to compare the two.

Like the 1965 code, Weizenbaum uses playful names for variables and functions: APRIL, JULY, VIRGIN, OBEY, RID. One distinctive trait is seen in his use of EQUIVALENCE statements. These are similar to C's union, where one location in memory can be accessed by two variable names with different types. Weizenbaum often uses a German and an English word with similar meanings to name these, as in this example:

           EQUIVALENCE (PLATZ,THERE)

Based on the non-contiguous line numbers and non-code file separators in ELIZA, it was likely originally prepared on punched cards and then loaded into CTSS for use under time-sharing. The 1967 code has perfectly regular line numbers, probably indicating it was entered online using an editor such as ED, which manages line numbers automatically as you edit a file. The 1967 code also has more comments and more blank lines, which is something you'd avoid on punched cards as it would make the deck physically larger.

Weizenbaum also uses more CTSS subroutines rather than relying solely on facilities in the MAD language. For example, he uses CTSS's PRMESA to print a message on the typewriter without a terminating carraige return.

The reconstruction

The first task was to OCR the PDF to extract machine readable text. Luckily recent LLMs do an amazing job on OCR, avoiding the hallucinations common even in mid 2025, and making around one mistake per page.

Then, the code was loaded into CTSS and compiled. This shook out the last of the OCR issues, some of them subtle. IBM in their wisdom used = to define decimal constants in assembly code and =O for octal constants, so =020 means decimal 20 but =O20 means octal 20, ie decimal 16, so this needed careful checking. No syntax errors were detected in the code, but when it came time to link/load (using the L command below) there was a problem:

l di eval (libe) opllib
W 1126.2
 NEED BOT    BRKEY  CNTSPC INITAS INLSTL IRARDR LEMPTY
      LIST   MTLIST NTHTOP ONELIN POPTOP RDLONL READER
      REMOVE SDBC   TODAY  TOP    LSLCPY LSSCPY LSTMRK
      MANY   MRKLST NAMTST NTHBOT NULSTL NULSTR OEPRNT
      POPBOT TXTPRT VCTLST NUCELL RCELL  ATEND  ATOMIC
      CONCAT TIME   REST   HIRANK CONS   REPLAC CALLS
      ADDKEY WASKEY NTOP   NBOT   MAX    MIN    FIRST
      SECOND INLSTR DSKLST DSKCLS LSTEQL NODLST LINLST
      STRLST YMATCH ASSMBL GETLIN FNDKEY
R .316+.033

61 functions were missing.

23 of these were SLIP primitives like BOT or LIST. We had a complete SLIP library on hand from the ELIZA reconstruction and its interface had been fairly stable since its first publication, so we could drop in the ELIZA-SLIP library easily.

Input/Output

More problematic were the input/output routines that were missing. The key ones here were RDLONL to read lines of input from the terminal and convert to a list, and TXTPRT to print a list to the terminal. We had similar code from ELIZA's TREAD and TXTPRT but they would not work directly with OPL. ELIZA really just cares about text but OPL needs support for numbers and symbols needed to express code in the language. So I chose to write these from scratch in MAD.

String handling on the IBM 7094 is deeply unfamiliar to modern programmers. The machine pre-dated ASCII and the 8-bit character: instead it uses 6-bit BCD characters, packed 6 to a 36-bit word. Even worse, there is no built-in facility to manipulate data at the character level, either in the MAD language or in assembly. Instead, characters need to be masked and bit-shifted to or from a word. Input and output also only occurs at a word level, so to output A B you have to prepare a word of 6 blanks, poke in the characters at bits 1-6 and 13-18, and then send the encoded word "A B " to the CTSS output subroutine.

By seeing what the EVAL function was expecting, it was possible to build working code for these functions. But lacking detailed examples of OPL code running, we do not know exactly how this looked. For example, we can see EVAL trying to output a list of two floating point numbers separated by a comma. To what precision should the numbers be printed? Should there be a space before or after the comma? Although the functionality of OPL is working as it did in 1967. how it looks on the terminal may be slightly different.

Data types

The next problem was data types. Each element of a SLIP list - called the datum - is a single 36-bit word and OPL allows text, symbols like *, integer and floating point numbers to be stored here. However, there is no unified way to tag what type the datum represents. Text data uses the SLIP indicator, which is two unused bits on the pointer to the next word in the list. Symbols are encoded as text but with the indicator turned off and padded with spaces, for example " *". Numbers also have no indicator, and Weizenbaum appeared to use a heuristic

           WHENEVER 77777K6 .A. DATUM .E. 0
              (treat datum as integer)
           OTHERWISE
              (treat datum as floating point)
           END OF CONDITIONAL

which basically means see if none of the bits marked as "x" in the word ...xxxxxxxxxxxxxxx.................. are set. FP numbers have the IBM 7094 equivalent of the exponent set here (note this is not the IEEE 754 floating point representation used today), whereas integers will not have these bits set unless they are very large.

The overlapping nature of this type detection was not documented in the code and had to be worked out by trial and error when developing the I/O routines: it's probable that there are still some mistakes in the reconstruction here.

Lambda function support

The printout had several instances where someone, presumably Weizenbaum, had made hand-written annotations.

/images/ctss/opl-annotation.png Example of an annotation to the OPL code.Source: MIT Libraries. License: MIT

These seem to concentrate around lines of code enabling lambda functions and the ability to create and call new anonymous functions. This feature does not appear in any of the OPL documentation and seems not to be fully working. I chose not to apply any of the hand written changes to the source code and left it as is.

There may be a connection here to the work Weizenbaum did on the funargs problem for his 1968 paper, though that only mentions the problem in the context of LISP. Joel Moses' 1970 paper The function of FUNCTION in LISP does provide some historical background that explicitly mentions OPL:

Joseph Weizenbaum got sufficiently interested in Landin's work that he implemented a subset of [the ISWIM lambda calculus] based on his SLIP-OPL system. The tree structure of the resulting stack was made quite vivid to me when Weizenbaum encountered difficulty in implementing the backward pointers necessary in the general case. The reason for this difficulty is that one could not then garbage collect the circular SLIP structures which are created in a straight-forward implementation. This is what motivated Weizenbaum to finally introduce classical garbage collection into SLIP [in 1969].

This would be a good topic for further research.

OPL utility functions

The above took care of most of the missing functions, but there were still several functions not used by the interpreter directly, but available to be called by the user's OPL program. Many of these were trivial to implement - eg MAX and MIN - so I have included implementations for around 15 of these. 18 functions remain unimplemented at this point, mainly either because the documentation is not clear on what they should do or if they are used by ELIZA but we lack the main ELIZA+OPL code so they would not be useful on stand-alone OPL.

Using OPL

See the GitHub repo for details on setting up the emulator and compiling the code.

Once done, you start OPL by typing R OPL. It responds with a greeting, the current date/time and a prompt of a line number. You can then enter one or more lines of text, ending your input with two carriage returns in a row.

In the below, my input is in lower case, OPL's is in capitals. Simple variable assignment and calculation:

r opl
W 1818.1
EXECUTION.
OPL AT YOUR SERVICE
DATE  02/16  TIME 1818.1
     1      a = 21, b = 2, c = a*b, type(c)

C               42

List manipulation

     2      x = '(10.0 20.0 30.0),
newtop(5.0, x),
popbot(x),
txtprt(x, 0)

5 10 20

Reproducing the mean example from the original paper

     3      define(mean(lst) =
      count = 0, total = 0,
      r = seqrdr(lst),
*loop item = seqlr(r),
      if item .e. 'nil then goto end :
      count = count + 1, total = total + item,
      goto loop, 
*end  total / count)

     4      type(mean(x))

MEAN(X)               11.66667

The logic is mostly the same, but note that parentheses are not needed and statements are separated by commas. seqlr does not return a flag, instead taking the value 'nil at the end of the list. Goto targets are now prefixed by *, and the IF statement is now a Fortran 66 style logical IF rather than the Fortran II three-way branch version. IF also needs to be terminated with a : as the THEN clause could have multiple statements separated by commas.

Sessions can be saved by pressing Interrupt (Control-Backslash under emulation) and then using the SAVE and RESTORE commands.

     5       QUIT,
R .133+.066

save myopl
W 1825.5
R .000+.033

r myopl
W 1825.6

     5      type(a, b, c)

A               21
B                2
C               42

The interpreter has some built in meta commands which start with a .. For example, .clock on enables display of how long an expression takes to complete evaluation.

     1      define(fac(n) = if n .le. 1 then 1 else n * fac(n - 1) )

     2      .clock on

     2      type(fac(10))

FAC(10)                3628800
TIME      66 MILLISECONDS

OPL will also detect missing definitions of variables and functions, and prompt for them to be supplied when needed.

     1      a1 = 10, a3 = 20, type(a1 + a2 + a3)

A1 + A2 + A3      IDENTIFIER  A2     UNDEFINED
      12

          42

It's important to mention that this is a snapshot of Weizenbaum's work, not a finished product. For example, undefined math results are not handled correctly and missing keywords, like DO in the IF statement below, can cause the program to crash.

     1      type(1/0)

1 / 0            0
     2      for j = 1 step 1 until j .e. 5 type(j)

J                1
 PROTECTION MODE VIOLATION AT 20163.
 INS.=050000063400, RI.=000000000000, PI.=053422000000
R .050+.050

I've put some more code examples in the samples directory of the Github repo, along with some details on known issues.

OPL's impact

I think it's fair to say that OPL did not have much of a direct impact. Weizenbaum never published OPL and it did not spread outside of MIT. A lot of different approaches were tried in computer aided instruction at this time, and the ELIZA+OPL scripting approach did not take off: more successful were ventures elsewhere such as the PLATO system and the Logo language. Weizenbaum himself spent less time on programming, instead working on his book Computer Power and Human Reason and getting involved in the political turmoil of the early 1970s.

It's always difficult to identify firsts in computer science history, as many people have similar ideas at the same time. I think the idea of a library (SLIP) having bindings in both a compiled (MAD) and an interpreted (OPL) language is a first, and embedding a scripting language like OPL in a domain specific language like ELIZA is also novel for its time. Certainly the ideas developed by Project MAC and CTSS were shared across the community and as a collection of ideas it would show its influence on later generations of time-sharing operating systems and interactive languages.

OPL remains interesting as one of the last examples of Weizenbaum's technical achievements, and it's interesting to think what could have happened had he developed it further.

Acknowledgements

Thanks to Anthony Hay, Arthur Schwarz, David Berry, Jeff Shrager, Mark Marino and everyone on RetroAI/Team ELIZA for their help and advice.

Thanks to MIT Libraries for preserving and scanning this code, and making it publicly available.

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


Next →