The program
ELIZA was written in MAD-SLIP: the MAD procedural language, extended with SLIP, Weizenbaum’s own Symmetric List Processor for handling lists. The recovered listing is about 420 lines. Reading it is an exercise in software archaeology.
MAD, abbreviated
MAD is a procedural language in the FORTRAN family, but with wordy keywords that programmers shortened: W'R for WHENEVER, T'O for TRANSFER TO, T'H for THROUGH. These contractions made sense at a teletype in 1965; decades later they are part of what makes the code hard to read. CTSS itself stored text in 6-bit BCD, packing six characters into a 36-bit word, a reminder of computing before ASCII was standard.
The heart of the loop
The main ELIZA function reads a sentence, hashes each word to find a keyword in the KEY table, matches the sentence with YMATCH (decomposition), and builds a reply with ASSMBL (reassembly). When nothing matches, it falls back to one of four holding phrases:
ELIZA MAD EXTERNAL FUNCTION (MYTRAN,KEY) NORMAL MODE IS INTEGER PRINT COMMENT $WHICH SCRIPT DO YOU WISH TO PLAY$ START LISTRD.(MTLIST.(INPUT),0) T'H SCAN, FOR WORD=POPTOP.(INPUT) I=HASH.(WORD,5) W'R LISTMT.(KEY(I)) .NE. 0, T'O SCAN S=SEQRDR.(KEY(I)) FOUND KEYWRD=SEQLR.(S,F) TRY W'R YMATCH.(TOP.(ES),INPUT,MTLIST.(TEST)) .E. 0, T'O MATCH MATCH ASSMBL.(REASMB,TEST,OUTPUT) TPRINT.(OUTPUT,0) T'O START NOMATCH(1) PRINT COMMENT $PLEASE CONTINUE $ NOMATCH(2) PRINT COMMENT $HMMM $ NOMATCH(3) PRINT COMMENT $GO ON , PLEASE $ NOMATCH(4) PRINT COMMENT $I SEE $
What the code revealed
Some things in the source were never explained in the published paper.
Weizenbaum wrote that a memory is recalled when “a certain counting mechanism is in a particular state”, and implied the choice was random. The source shows otherwise: a counter cycles 1–4 through the replies, and only on 4 is a memory recalled; the memory template is chosen by a hash of the last word you typed. Nothing random about it. Read more ›
The recovered version contains a CHANGE function, an interactive editor that let a user rewrite ELIZA’s script while it was running. It was barely mentioned in the 1966 paper and seems to have been lost from every later version. It anticipates, by decades, the idea of a system you reshape by talking to it.
Reconstruction
Several functions were missing from the recovered listing and had to be carefully rebuilt: BCDIT (binary to BCD), INLST (used by matching and assembly), LETTER (character classification), and various initialisation routines. The reconstructed ELIZA now runs on an emulated CTSS. As the team behind the emulation put it, “running the original code feels good and authentic. Finding bugs in it only adds to the authenticity.” On reconstruction ›
We read the source the way Critical Code Studies reads any code: as a text, within its historical and material context, where naming and structure carry meaning, and so, often, do the bugs. The command vocabulary of the editor (TYPE, SUBST, APPEND, ADD, START, RANK, DISPLA) is a small grammar of power; the iteration over a fixed table is a trace of the machine’s limits. Reading ELIZA: CCS in Action ›
The complete recovered listing, ELIZA-1965b.mad, in MAD-SLIP, as a line-printer printout. This is the 1965b version from the printout in Weizenbaum’s archive (it adds “but” as a delimiter and lacks the NEWKEY function), and the basis for the close reading above. Also in the repository and the MIT archive.
CHANGE MAD
EXTERNAL FUNCTION (KEY,MYTRAN) 000010
NORMAL MODE IS INTEGER 000020
ENTRY TO CHANGE. 000030
LIST.(INPUT) 000040
V'S G(1)=$TYPE$,$SUBST$,$APPEND$,$ADD$, 000050
1$START$,$RANK$,$DISPLA$ 000060
V'S SNUMB = $ I3 *$ 000070
FIT=0 000080
CHANGE PRINT COMMENT $PLEASE INSTRUCT ME$ 001400
LISTRD.(MTLIST.(INPUT),0) 001410
JOB=POPTOP.(INPUT) 001420
T'H IDENT, FOR J=1,1, J.G. 7 001430
IDENT W'R G(J) .E. JOB, T'O THEMA 001440
PRINT COMMENT $CHANGE NOT RECOGNIZED$ 001450
T'O CHANGE 001460
THEMA W'R J .E. 5, F'N IRALST.(INPUT) 001470
W'R J .E. 7 001480
T'H DISPLA, FOR I=0,1, I .G. 32 001490
W'R LISTMT.(KEY(I)) .E. 0, T'O DISPLA 001500
S=SEQRDR.(KEY(I)) 001510
READ(7) NEXT=SEQLR.(S,F) 001520
W'R F .G. 0, T'O DISPLA 001530
PRINT COMMENT $*$ 001540
TPRINT.(NEXT,0) 001550
PRINT FORMAT SNUMB,I 001560
PRINT COMENT $ $ 001570
T'O READ(7) 001580
DISPLA CONTINUE 001590
PRINT COMMENT $ $ 001600
PRINT COMMENT $MEMORY LIST FOLLOWS$ 001610
PRINT COMMENT $ $ 001620
T'H MEMLIST, FOR I=1 , 1, I .G. 4 001630
MEMLST TXTPRT.(MYTRAN(I),0) 001640
T'O CHANGE 001650
E'L 001660
THEME=POPTOP.(INPUT) 001670
SUBJECT=KEY(HASH.(THEME,5)) 001680
S=SEQRDR.(SUBJECT) 001690
LOOK TERM=SEQLR.(S,F) 001700
W'R F .G. 0, T'O FAIL 001710
W'R TOP.(TERM) .E. THEME, T'O FOUND 001720
T'O LOOK 001730
FOUND T'O DELTA(J) 001740
DELTA(1) TPRINT.(TERM,0) 001750
T'O CHANGE 001760
FAIL PRINT COMMENT $LIST NOT FOUND$ 001770
T'O CHANGE 001780
DELTA(2) S=SEQRDR.(TERM) 001790
OLD=POPTOP.(INPUT) 001800
READ(1) OBJCT=SEQLR.(S,F) 001810
W'R F .G. 0, T'O FAIL 001820
W'R F .NE. 0, T'O READ(1) 001830
INSIDE=SEQRDR.(OBJECT) 001840
READ(2) IT=SEQLR.(INSIDE,F) 001850
W'R F .G. 0, T'O READ(1) 001860
SIT=SEQRDR.(IT) 001870
SOLD=SEQRDR.(OLD) 001880
ITOLD TOLD=SEQLR.(SOLD,FOLD) 001890
DIT=SEQLR.(SIT,FIT) 001900
W'R TOLD .E. DIT .AND. FOLD .LE. 0,T'O ITOLD 001910
W'R FOLD .G. 0, T'O OK(J) 001920
T'O READ(2) 001930
OK(2) SUBST.(POPTOP.(INPUT),LSPNTR.(INSIDE)) 001940
T'O CHANGE 001950
OK(3) NEWBOT.(POPTOP.(INPUT),OBJCT) 001960
T'O CHANGE 001970
DELTA(3) T'O DELTA(2) 001980
DELTA(4) W'R NAMTST.(BOT.(TERM)) .E. 0 001990
BOTTOM=POPBOT.(TERM) 002000
NEWBOT.(POPTOP.(INPUT),TERM) 002010
NEWBOT.(BOTTOM,TERM) 002020
O'E 002030
NEWBOT.(POPTOP.(INPUT),TERM) 002040
E'L 002050
T'O CHANGE 002060
DELTA(6) S=SEQRDR.(TERM) 002070
READ(6) OBJCT=SEQLR.(S,F) 002080
W'R F .G. 0, T'O FAIL 002090
W'R F .NE. 0, T'O READ(6) 002100
OBJCT=SEQLL.(S,F) 002110
W'R LNKLL.(OBJECT) .E. 0 002120
SUBST.(POPTOP.(INPUT),LSPNTR.(S)) 002130
O'E 002140
NEWTOP.(POPTOP.(INPUT),LSPNTR.(S)) 002150
E'L 002160
T'O CHANGE 002170
R* * * * * * * * * * END OF MODIFICATION ROUTINE 002180
E'N 002200
TPRINT MAD
EXTERNAL FUNCTION (LST) 000010
NORMAL MODE IS INTEGER 000020
ENTRY TO TPRINT. 000030
SA=SEQRDR.(LST) 000040
LIST.(OUT) 000050
READ NEXT=SEQLR.(SA,FA) 000060
W'R FA .G. 0, T'O P 000070
W'R FA .E. 0, T'O B 000080
POINT=NEWBOT.(NEXT,OUT) 000100
W'R SA .L. 0, MRKNEG.(POINT) 000110
T'O READ 000120
B TXTPRT.(OUT,0) 000130
SEQLL.(SA,FA) 000140
MORE NEXT=SEQLR.(SA,FA) 000150
W'R TOP.(NEXT) .E. $=$ 000160
TXTPRT.(NEXT,0) 000170
T'O MORE 000180
E'L 000190
W'R FA .G. 0, T'O DONE 000200
PRINT COMMENT $ $ 000210
SB=SEQRDR.(NEXT) 000220
MEHR TERM=SEQLR.(SB,FB) 000230
W'R FB .L.0 000240
PRINT ON LINE FORMAT NUMBER, TERM 000250
V'S NUMBER = $I3 *$ 000260
T'O MEHR 000270
E'L 000280
W'R FB .G. 0, T'O MORE 000290
TXTPRT.(TERM,0) 000300
T'O MEHR 000310
P TXTPRT.(OUT,0) 000320
DONE IRALST.(OUT) 000330
F'N 000340
E'N 000350
LPRINT MAD
EXTERNAL FUNCTION (LST,TAPE) 006340
NORMAL MODE IS INTEGER 006350
ENTRY TO LPRINT. 006360
BLANK = $ $ 006370
EXECUTE PLACE.(TAPE,0) 006380
LEFTP = 606074606060K 006390
RIGHTP= 606034606060K 006400
BOTH = 607460603460K 006410
EXECUTE NEWTOP.(SEQRDR.(LST),LIST.(STACK)) 006420
S=POPTOP.(STACK) 006430
BEGIN EXECUTE PLACE.(LEFTP,1) 006440
NEXT WORD=SEQLR.(S,FLAG) 006450
W'R FLAG .L. 0 006460
EXECUTE PLACE.(WORD,1) 006470
W'R S .G. 0, PLACE.(BLANK,1) 006480
T'O NEXT 006490
OR W'R FLAG .G. 0 006500
EXECUTE PLACE.(RIGHTP,1) 006510
W'R LISTMT.(STACK) .E. 0, T'O DONE 006520
S=POPTOP.(STACK) 006530
T'O NEXT 006540
OTHERWISE 006550
W'R LISTMT.(WORD) .E. 0 006560
EXECUTE PLACE.(BOTH,1) 006570
T'O NEXT 006580
OTHERWISE 006590
EXECUTE NEWTOP.(S,STACK) 006600
S=SEQRDR.(WORD) 006610
T'O BEGIN 006620
E'L 006630
E'L 006640
DONE EXECUTE PLACE.(0,-1) 006650
EXECUTE IRALST.(STACK) 006660
FUNCTION RETURN LST 006670
END OF FUNCTION 006680
TESTS MAD
EXTERNAL FUNCTION(CAND,S) 000010
NORMAL MODE IS INTEGER 000020
DIMENSION FIRST(5),SECOND(5) 000030
ENTRY TO TESTS. 000040
STORE=S 000050
READER=SEQRDR.(CAND) 000060
T'H ONE, FOR I=0,1, I .G. 100 000070
FIRST(I)=SEQLR.(READER,FR) 000080
ONE W'R READER .G. 0, T'O ENDONE 000090
ENDONE SEQLL.(S,F) 000100
T'H TWO, FOR J=0,1, J .G. 100 000110
SECOND(J)=SEQLR.(S,F) 000120
TWO W'R S .G. 0, T'O ENDTWO 000130
ENDTWO W'R I .NE. J, F'N 0 000140
T'H LOOK, FOR K=0,1, K.G. J 000150
LOOK W'R FIRST(K) .NE. SECOND(K), F'N 0 000170
EQL=SEQLR.(READER,FR) 000180
W'R EQL .NE. $=$ 000190
SEQLL.(READER,FR) 000200
F'N READER 000210
O'E 000220
POINT=LNKL.(STORE) 000230
T'H DELETE , FOR K=0,1, K .G. J 000240
REMOVE.(LSPNTR.(STORE)) 000250
DELETE SEQLR.(STORE,F) 000260
INSRT NEW=SEQLR.(READER,FR) 000270
POINT=NEWTOP.(NEW,POINT) 000280
MRKNEG.(POINT) 000290
W'R READER .L. 0, T'O INSRT 000300
MRKPOS.(POINT) 000310
F'N READER 000320
E'L 000330
E'N 000340
DOCBCD MAD
EXTERNAL FUNCTION (A,B) 000010
NORMAL MODE IS INTEGER 000020
ENTRY TO FRBCD. 000030
W'R LNKL.(A) .E. 0, T'O NUMBER 000040
B=A 000050
F'N 0 000060
NUMBER K=A*262144 000070
B=BCDIT.(K) 000080
F'N 0 000090
E'N 000100
ELIZA MAD
NORMAL MODE IS INTEGER 000010
DIMENSION KEY(32),MYTRAN(4) 000020
INITAS.(0) 000030
PRINT COMMENT $WHICH SCRIPT DO YOU WISH TO PLAY$ 000060
READ FORMAT SNUMB,SCRIPT 000070
LIST.(TEST) 000080
LIST.(INPUT) 000090
LIST.(OUTPUT) 000100
LIST.(JUNK) 000110
LIMIT=1 000120
LSSCPY.(THREAD.(INPUT,SCRIPT),JUNK) 000130
MTLIST.(INPUT) 000140
T'H MLST, FOR I=1,1, I .G. 4 000150
MLST LIST.(MYTRAN(I)) 000160
MINE=0 000170
LIST.(MYLIST) 000180
T'H KEYLST, FOR I=0,1, I .G. 32 000220
KEYLST LIST.(KEY(I)) 000230
R* * * * * * * * * * READ NEW SCRIPT 000240
BEGIN MTLIST.(INPUT) 000250
NODLST.(INPUT) 000260
LISTRD.(INPUT,SCRIPT) 000270
W'R LISTMT.(INPUT) .E. 0 000280
TXTPRT.(JUNK,0) 000290
MTLIST.(JUNK) 000300
T'O START 000310
E'L 000320
W'R TOP.(INPUT) .E. $NONE$ 000330
NEWTOP.(LSSCPY.(INPUT,LIST.(9)),KEY(32)) 000340
T'O BEGIN 000350
OR W'R TOP.(INPUT) .E. $MEMORY$ 000360
POPTOP.(INPUT) 000370
MEMORY=POPTOP.(INPUT) 000380
T'H MEM, FOR I=1,1, I .G. 4 000390
MEM LSSCPY.(POPTOP.(INPUT),MYTRAN(I)) 000400
T'O BEGIN 000410
O'E 000420
NEWBOT.(LSSCPY.(INPUT,LIST.(9)),KEY(HASH. 000430
1 (TOP.(INPUT),5))) 000440
T'O BEGIN 000450
E'L 000460
R* * * * * * * * * * BEGIN MAJOR LOOP 000470
START TREAD.(MTLIST.(INPUT),0) 000480
KEYWRD=0 000490
PREDNC=0 000500
LIMIT=LIMIT+1 000510
W'R LIMIT .E. 5, LIMIT=1 000520
W'R LISTMT.(INPUT) .E. 0, T'O ENDPLA 000530
IT=0 000540
W'R TOP.(INPUT) .E. $+$ 000550
CHANGE.(KEY,MYTRAN) 000560
T'O START 000570
E'L 000580
W'R TOP.(INPUT) .E. $*$, T'O NEWLST 000590
S=SEQRDR.(INPUT) 000600
NOTYET W'R S .L. 0 000610
SEQLR.(S,F) 000620
T'O NOTYET 000630
O'E 000640
WORD=SEQLR.(S,F) 000650
W'R WORD .E. $.$ .OR. WORD .E. $,$ .OR. WORD .E. $BUT$ 000660
W'R IT .E. 0 000670
NULSTL.(INPUT,LSPNTR.(S),JUNK) 000680
MTLIST.(JUNK) 000690
T'O NOTYET 000700
O'E 000710
NULSTR.(INPUT,LSPNTR.(S),JUNK) 000720
MTLIST.(JUNK) 000730
T'O ENDTXT 000740
E'L 000750
E'L 000760
E'L 000770
W'R F .G. 0, T'O ENDTXT 000780
I=HASH.(WORD,5) 000790
SCANER=SEQRDR.(KEY(I)) 000800
SF=0 000810
T'H SEARCH, FOR J=0,0, SF .G. 0 000820
CAND= SEQLR.(SCANER,SF) 000830
W'R SF .G. 0, T'O NOTYET 000840
SEARCH W'R TOP.(CAND) .E. WORD, T'O KEYFND 000850
KEYFND READER=TESTS.(CAND,S) 000860
W'R READER .E. 0, T'O NOTYET 000870
W'R LSTNAM.(CAND) .NE. 0 000880
DL=LSTNAM.(CAND) 000890
SEQ W'R S .L. 0 000900
SEQLR.(S,F) 000910
T'O SEQ 000920
O'E 000930
NEWTOP.(DL,LSPNTR.(S)) 000940
E'L 000950
O'E 000960
E'L 000970
NEXT=SEQLR.(READER,FR) 000980
W'R FR .G. 0, T'O NOTYET 000990
W'R IT .E. 0 .AND. FR .E. 0 001000
PLCKEY IT=READER 001010
KEYWRD=WORD 001020
OR W'R FR .L. 0 .AND. NEXT .G. PREDNC 001030
PREDNC=NEXT 001040
NEXT=SEQLR.(READER,FR) 001050
T'O PLCKEY 001060
O'E 001070
T'O NOTYET 001080
E'L 001090
T'O NOTYET 001100
R* * * * * * * * * * END OF MAJOR LOOP 001110
ENDTXT W'R IT .E. 0 001120
W'R LIMIT .E. 4 .AND. LISTMT.(MYLIST) .NE. 0 001130
OUT=POPTOP.(MYLIST) 001140
TXTPRT.(OUT,0) 001150
IRALST.(OUT) 001160
T'O START 001170
O'E 001180
ES=BOT.(TOP.(KEY(32))) 001190
T'O TRY 001200
E'L 001210
OR W'R KEYWRD .E. MEMORY 001220
I=HASH.(BOT.(INPUT),2)+1 001230
NEWBOT.(REGEL.(MYTRAN(I),INPUT,LIST.(MINE)),MYLIST) 001240
SEQLL.(IT,FR) 001250
T'O MATCH 001260
O'E 001270
SEQLL.(IT,FR) 001280
R* * * * * * * * * * MATCHING ROUTINE 001290
MATCH ES=SEQLR.(IT,FR) 001300
W'R TOP.(ES) .E. $=$ 001310
S=SEQRDR.(ES) 001320
SEQLR.(S,F) 001330
WORD=SEQLR.(S,F) 001340
I=HASH.(WORD,5) 001350
SCANER=SEQRDR.(KEY(I)) 001360
SCAN ITS=SEQLR.(SCANER,F) 001370
W'R F .G. 0, T'O NOMATCH(LIMIT) 001380
W'R WORD .E. TOP.(ITS) 001390
S=SEQRDR.(ITS) 001400
SCANI ES=SEQLR.(S,F) 001410
W'R F .NE.0, T'O SCANI 001420
IT=S 001430
T'O TRY 001440
O'E 001450
T'O SCAN 001460
E'L 001470
E'L 001480
W'R FR .G. 0, T'O NOMATCH(LIMIT) 001490
TRY W'R YMATCH.(TOP.(ES),INPUT,MTLIST.(TEST)) .E. 0,T'O MATCH 001500
ESRDR=SEQRDR.(ES) 001510
SEQLR.(ESRDR,ESF) 001520
POINT=SEQLR.(ESRDR,ESF) 001530
POINTR=LSPNTR.(ESRDR) 001540
W'R ESF .E. 0 001550
NEWBOT.(1,POINTR) 001560
TRANS=POINT 001570
T'O HIT 001580
O'E 001590
T'H FNDHIT,FOR I=0,1, I .G. POINT 001600
FNDHIT TRANS=SEQLR.(ESRDR,ESF) 001610
W'R ESF .G. 0 001620
SEQLR.(ESRDR,ESF) 001630
SEQLR.(ESRDR,ESF) 001640
TRANS=SEQLR.(ESRDR,ESF) 001650
SUBST.(1,POINTR) 001660
T'O HIT 001670
O'E 001680
SUBST.(POINT+1,POINTR) 001690
T'O HIT 001700
E'L 001710
E'L 001720
HIT TXTPRT.(ASSMBL.(TRANS,TEST,MTLIST.(OUTPUT)),0) 001730
T'O START 001740
E'L 001750
R* * * * * * * * * * INSERT NEW KEYWORD LIST 001760
NEWLST POPTOP.(INPUT) 001770
NEWBOT.(LSSCPY.(INPUT,LIST.(9)),KEY(HASH. 001780
1(TOP.(INPUT),5))) 001790
T'O START 001800
R* * * * * * * * * * DUMP REVISED SCRIPT 001810
ENDPLA PRINT COMMENT $WHAT IS TO BE THE NUMBER OF THE NEW SCRIPT$ 001820
READ FORMAT SNUMB,SCRIPT 001830
LPRINT.(INPUT,SCRIPT) 001840
NEWTOP.(MEMORY,MTLIST.(OUTPUT)) 001850
NEWTOP.($MEMORY$,OUTPUT) 001860
T'H DUMP, FOR I=1,1 I .G. 4 001870
DUMP NEWBOT.(MYTRAN(I),OUTPUT) 001880
LPRINT.(OUTPUT,SCRIPT) 001890
MTLIST.(OUTPUT) 001900
T'H WRITE, FOR I=0,1, I .G. 32 001910
POPMOR W'R LISTMT.(KEY(I)) .E. 0, T'O WRITE 001920
LPRINT.(POPTOP.(KEY(I)),SCRIPT) 001930
T'O POPMOR 001940
WRITE CONTINUE 001950
LPRINT.(MTLIST.(INPUT),SCRIPT) 001960
EXIT. 001970
R* * * * * * * * * * SCRIPT ERROR EXIT 001980
NOMATCH(1) PRINT COMMENT $PLEASE CONTINUE $ 002200
T'O START 002210
NOMATCH(2) PRINT COMMENT $HMMM $ 002220
T'O START 002230
NOMATCH(3) PRINT COMMENT $GO ON , PLEASE $ 002240
T'O START 002250
NOMATCH(4) PRINT COMMENT $I SEE $ 002260
T'O START 002270
VECTOR VALUES SNUMB= $I3 * $ 002280
E'M 002290