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.
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 toEVALfor 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 FAPthat contains several FAP assembly language files that support OPL. An example of these isOBEY FAPwhich 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.
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
PDP-10 with several DECtapes. Source: