mirror of
https://github.com/kennethreitz/elizagen.org.git
synced 2026-06-21 15:10:57 +00:00
909 lines
54 KiB
Plaintext
909 lines
54 KiB
Plaintext
;
|
|
; ELIZA by Joseph Weizenbaum.
|
|
;
|
|
; Any line beginning with a semicolon is commentary and was not part of
|
|
; the original ELIZA code. Each commentary block generally refers to the
|
|
; code just above it.
|
|
;
|
|
|
|
|
|
|
|
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(CAND, S) return a sequence reader if the keyword matches the user's
|
|
; input text, otherwise return 0.
|
|
;
|
|
; CAND is the keyword candidate transformation rule
|
|
; S is the sequence reader for the user INPUT text
|
|
;
|
|
; This function has 3 tasks
|
|
;
|
|
; 1. Test whether the whole candidate keyword matches the whole word
|
|
; in the user's input text.
|
|
; 2. If the words do match, make any keyword substitution specified
|
|
; in the candidate transformation rule.
|
|
; 3. Position the candidate reader past the substitution keyword, if any.
|
|
;
|
|
; SLIP packs 6 6-bit characters into each 36-bit IBM 7094 machine word.
|
|
; If a word has more than 6 characters it is continued into the next SLIP
|
|
; cell, with the first cell having its sign bit set. ???
|
|
;
|
|
; This code abstracts this full-word matching and has the side-effect
|
|
; of modifying the user's input text with the substitution word, if
|
|
; specified.
|
|
;
|
|
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
|
|
;
|
|
; Copy all 6-character chunks of the candidate keyword to the FIRST array.
|
|
;
|
|
; [As the loop termination condition is I .G. 100 (000070), this code will
|
|
; write past the end of the FIRST array if the keyword is longer than 36
|
|
; characters (because the first 36 characters will be copied to
|
|
; FIRST(0) .. FIRST(5), and any further characters will be written to
|
|
; machine words past FIRST(5)).]
|
|
;
|
|
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
|
|
;
|
|
; Copy all 6-character chunks of the user input word to the SECOND array.
|
|
; [May write past the end of SECOND.]
|
|
;
|
|
ENDTWO W'R I .NE. J, F'N 0 000140
|
|
;
|
|
; If the keyword in FIRST has a different number of 6-character chunks to
|
|
; the word in SECOND the two words cannot be the same, so return the value 0,
|
|
; signifying no match.
|
|
;
|
|
; W'R is an abbreviation of WHENEVER
|
|
; .NE. means not equal
|
|
; F'N is an abbreviation of FUNCTION RETURN
|
|
;
|
|
T'H LOOK, FOR K=0,1, K.G. J 000150
|
|
LOOK W'R FIRST(K) .NE. SECOND(K), F'N 0 000170
|
|
;
|
|
; Compare each 6-character chunk of the keyword with the corresponding chunk
|
|
; of the user input word. If any are different, return 0, signifying no match.
|
|
;
|
|
EQL=SEQLR.(READER,FR) 000180
|
|
W'R EQL .NE. $=$ 000190
|
|
SEQLL.(READER,FR) 000200
|
|
F'N READER 000210
|
|
O'E 000220
|
|
;
|
|
; At this point we know that the keyword matches the user's word.
|
|
; Check whether the transformation rules specify a simple word substitution,
|
|
; signified by the presence of an "=".
|
|
;
|
|
; If it is not an "=", reposition the reader back before the element and
|
|
; return the reader, signifying a successful match.
|
|
;
|
|
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
|
|
;
|
|
; An "=" was present in the transformation rule. E.g. a script
|
|
; transformation rule may begin
|
|
;
|
|
; (YOUR = MY
|
|
; ((0 MY 0)
|
|
; (WHY ARE YOU CONCERNED OVER MY 3)
|
|
; (WHAT ABOUT YOUR OWN 3)
|
|
; :
|
|
;
|
|
; Say at this point the keyword YOUR has been found in the user's input text
|
|
; and we know that in the transformation rule the keyword (YOUR) is followed
|
|
; by an "=". So we're now going to replace the YOUR in the input text with
|
|
; the word following the "=" in the transformation rule (MY, in this case).
|
|
;
|
|
; First delete all the 6-character chunks that comprise this word, then
|
|
; insert all the 6-character chunks that comprise the replacement word.
|
|
;
|
|
; Finally, return the reader, signifying a successful match.
|
|
;
|
|
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 entry point.
|
|
;
|
|
ELIZA MAD
|
|
NORMAL MODE IS INTEGER 000010
|
|
DIMENSION KEY(32),MYTRAN(4) 000020
|
|
;
|
|
; KEY - A hashmap used to record keywords.
|
|
; KEY(0)..KEY(31) is the keyword->transformation rule hashmap
|
|
; KEY(32) is the "NONE" transformation rule
|
|
;
|
|
; MYTRAN - A hashmap used to record the MEMORY rules.
|
|
; MYTRAN(1)..MYTRAN(4) contain the four MEMORY rules.
|
|
;
|
|
; A note on MAD arrays: DIMENSION D(N) allocates N+1 machine-words of
|
|
; core memory, which are accessed using indexes 0..N.
|
|
;
|
|
INITAS.(0) 000030
|
|
;
|
|
; INITAS must be the first executable statement in any program using SLIP.
|
|
; Its purpose is to create the List of Available Space from all unused
|
|
; core memory. It does not require an argument, but here is given 0.
|
|
;
|
|
PRINT COMMENT $WHICH SCRIPT DO YOU WISH TO PLAY$ 000060
|
|
READ FORMAT SNUMB,SCRIPT 000070
|
|
;
|
|
; Display the message "WHICH SCRIPT DO YOU WISH TO PLAY".
|
|
;
|
|
; Note that the IBM 7090/7094 character set doesn't include a question
|
|
; mark glyph. Also $ is used to delimit character strings.
|
|
;
|
|
; SNUMB is the FORTRAN format string " I3 *", defined previously, which
|
|
; expects the user to enter up to 3 decimal digits. This number is assigned
|
|
; to the variable SCRIPT and will be used as the tape drive unit number
|
|
; where the ELIZA script is expected to reside.
|
|
;
|
|
LIST.(TEST) 000080
|
|
LIST.(INPUT) 000090
|
|
LIST.(OUTPUT) 000100
|
|
LIST.(JUNK) 000110
|
|
;
|
|
; Initialise four lists. These are:
|
|
; TEST - Used to store the parts of the user's text matching a
|
|
; decomposition rule.
|
|
; INPUT - During ELIZA startup the selected script is read into this list,
|
|
; one round-bracketed list at a time.
|
|
; During the conversation phase the text entered by the user is
|
|
; read into this list.
|
|
; OUTPUT - ELIZA's response sentence is constructed in this list.
|
|
; JUNK - A list used for temporary storage for several different purposes.
|
|
;
|
|
LIMIT=1 000120
|
|
;
|
|
; When Weizenbaum talks in the January 1966 CACM paper of a "certain counting
|
|
; mechanism", it is this to which he is referring. LIMIT has the value 1..4,
|
|
; in order, and then restarts at 1. The value changes to the next in the
|
|
; sequence at each user input. More on LIMIT below.
|
|
;
|
|
LSSCPY.(THREAD.(INPUT,SCRIPT),JUNK) 000130
|
|
MTLIST.(INPUT) 000140
|
|
;
|
|
; The THREAD function reads text from the tape unit specified by the integer
|
|
; SCRIPT into the INPUT list. The LSSCPY function copies the first list in
|
|
; that INPUT to the list named JUNK.
|
|
;
|
|
; The first list in an ELIZA script must be the hello message, e.g.
|
|
; (HOW DO YOU DO. PLEASE TELL ME YOUR PROBLEM).)
|
|
;
|
|
T'H MLST, FOR I=1,1, I .G. 4 000150
|
|
MLST LIST.(MYTRAN(I)) 000160
|
|
;
|
|
; Initialise each of the four MYTRAN array entries as a new list.
|
|
;
|
|
; T'H is an abbreviation for THROUGH
|
|
; .G. is the Boolean grater than operator
|
|
;
|
|
; Set I to 1, if I is greater than 4 stop looping, otherwise execute the code
|
|
; up to and including the statement labelled MLST. Then add 1 to I and return
|
|
; to the top of the loop at the point of the test to see if I is greater than
|
|
; 4 and repeat.
|
|
;
|
|
; for I in 1..4 {
|
|
; call function LIST with argument a reference
|
|
; to the Ith entry in the MYTRAN array
|
|
; }
|
|
;
|
|
MINE=0 000170
|
|
LIST.(MYLIST) 000180
|
|
;
|
|
; MINE - Set to 0 and is never changed. It's referenced once below. ???
|
|
; MYLIST - As memories are made using the MYTRAN MEMORY rules they are
|
|
; recorded in MYLIST. Here MYLIST is being initialised as a new
|
|
; empty list.
|
|
;
|
|
T'H KEYLST, FOR I=0,1, I .G. 32 000220
|
|
KEYLST LIST.(KEY(I)) 000230
|
|
;
|
|
; Initialise each of KEY(0) .. KEY(32) array entries as a new list.
|
|
; for I in 0..32 {
|
|
; call function LIST with argument a reference
|
|
; to the Ith entry in the KEY array
|
|
; }
|
|
;
|
|
R* * * * * * * * * * READ NEW SCRIPT 000240
|
|
BEGIN MTLIST.(INPUT) 000250
|
|
NODLST.(INPUT) 000260
|
|
LISTRD.(INPUT,SCRIPT) 000270
|
|
;
|
|
; Empty the INPUT list. Remove the description list??? from INPUT (NODLST).
|
|
; Read the next round-bracket-delimited list from tape unit id SCRIPT.
|
|
;
|
|
W'R LISTMT.(INPUT) .E. 0 000280
|
|
TXTPRT.(JUNK,0) 000290
|
|
MTLIST.(JUNK) 000300
|
|
T'O START 000310
|
|
E'L 000320
|
|
;
|
|
; W'R is an abbreviation of WHENEVER.
|
|
; .E. means equals.
|
|
; T'O is an abbreviation for TRANSFER TO.
|
|
; E'L is an abbreviation of END OF CONDITIONAL.
|
|
;
|
|
; An empty list signals the end of the ELIZA script. (Which is presumably
|
|
; why there is () on the last line of the published DOCTOR script.)
|
|
;
|
|
; if INPUT is the empty list {
|
|
; (the whole ELIZA script has now been read and processed)
|
|
; print the value of JUNK, e.g. "HOW DO YOU DO. PLEASE TELL ME YOUR PROBLEM"
|
|
; clear the JUNK list
|
|
; goto the START label
|
|
; }
|
|
;
|
|
W'R TOP.(INPUT) .E. $NONE$ 000330
|
|
NEWTOP.(LSSCPY.(INPUT,LIST.(9)),KEY(32)) 000340
|
|
T'O BEGIN 000350
|
|
;
|
|
; If this list is the special "NONE" list, just copy it unchanged into KEY(32)
|
|
; and then goto BEGIN to read the next list in the script.
|
|
;
|
|
; Recall that the NONE list in the DOCTOR script is:
|
|
; (NONE
|
|
; ((0)
|
|
; (I AM NOT SURE I UNDERSTAND YOU FULLY)
|
|
; (PLEASE GO ON)
|
|
; (WHAT DOES THAT SUGGEST TO YOU)
|
|
; (DO YOU FEEL STRONGLY ABOUT DISCUSSING SUCH THINGS)))
|
|
;
|
|
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
|
|
;
|
|
; Otherwise, if this list is the special "MEMORY" list, process it into the
|
|
; four MYTRAN lists. Recall that the MEMORY list looks like this and is
|
|
; required to have exactly four transformation patterns:
|
|
; (MEMORY MY
|
|
; (0 YOUR 0 = LETS DISCUSS FURTHER WHY YOUR 3)
|
|
; (0 YOUR 0 = EARLIER YOU SAID YOUR 3)
|
|
; (0 YOUR 0 = BUT YOUR 3)
|
|
; (0 YOUR 0 = DOES THAT HAVE ANYTHING TO DO WITH THE FACT THAT YOUR 3))
|
|
;
|
|
; else if the first word in INPUT is "MEMORY" {
|
|
; assign the memory keyword (e.g. "MY") to the MEMORY variable
|
|
; for I in 1..4 {
|
|
; copy the Ith MEMORY pattern/reconstruction to MYTRAN(I)
|
|
; }
|
|
; goto the BEGIN label (continue reading the ELIZA script)
|
|
; }
|
|
;
|
|
O'E 000420
|
|
NEWBOT.(LSSCPY.(INPUT,LIST.(9)),KEY(HASH. 000430
|
|
1 (TOP.(INPUT),5))) 000440
|
|
T'O BEGIN 000450
|
|
E'L 000460
|
|
;
|
|
; Otherwise, the first word in the INPUT list is expected to be a keyword.
|
|
; Insert this keyword into the KEY hashtable, so that
|
|
; KEY(HASH(keyword)) -> list of transformation rules for keywords
|
|
; that hash to this entry in KEY (i.e. more than
|
|
; one keyword may hash to the same entry in KEY,
|
|
; so each entry in KEY may have zero, one or many
|
|
; keyword transformation rules associated with it.)
|
|
;
|
|
; (1 in column 11 signifies a continuation of the previous line.)
|
|
;
|
|
; The HASH function takes a word and a number (N) and returns a deterministic
|
|
; value between 0 and (2 to the power N)-1, in this case 0..31.
|
|
;
|
|
; else {
|
|
; HASH the keyword and append this transformation rule to the
|
|
; entry in KEY with that index
|
|
; goto the BEGIN label (continue reading the ELIZA script)
|
|
; }
|
|
;
|
|
; This is the end of the script reading code. When the script has been
|
|
; read and processed the script reader explicitly jumps to the START label
|
|
; to begin the user conversation.
|
|
;
|
|
R* * * * * * * * * * BEGIN MAJOR LOOP 000470
|
|
START TREAD.(MTLIST.(INPUT),0) 000480
|
|
;
|
|
; Wait for the user to type a sentence and read it into the INPUT list,
|
|
; which is first cleared. Presumably, tape unit 0 is the console.
|
|
;
|
|
; TREAD is the SLIP system text read function.
|
|
;
|
|
KEYWRD=0 000490
|
|
PREDNC=0 000500
|
|
;
|
|
; KEYWRD - This will be the keyword found to have the highest precedence.
|
|
; PREDNC - The precedence of the keyword. Precedence is specified in the
|
|
; ELIZA script. E.g. (DREAMS = DREAM 3 (=DREAM)), the keyword
|
|
; DREAMS is given the precedence value 3.
|
|
;
|
|
LIMIT=LIMIT+1 000510
|
|
W'R LIMIT .E. 5, LIMIT=1 000520
|
|
;
|
|
; Increment the value of LIMIT. If it then equals 5, set it back to 1.
|
|
; If we just read the very first user input, LIMIT will now have the value 2.
|
|
;
|
|
W'R LISTMT.(INPUT) .E. 0, T'O ENDPLA 000530
|
|
;
|
|
; If the user input is a blank line, goto the ENDPLA label.
|
|
; A blank user input tells ELIZA the conversation is over.
|
|
;
|
|
IT=0 000540
|
|
;
|
|
; IT - On exit from the scanning loop IT will either be the sequence
|
|
; reader for the selected transformation rule, or it will be 0
|
|
; indicating that no keyword was detected in the user's INPUT.
|
|
;
|
|
W'R TOP.(INPUT) .E. $+$ 000550
|
|
CHANGE.(KEY,MYTRAN) 000560
|
|
T'O START 000570
|
|
E'L 000580
|
|
;
|
|
; If first word of the user input is a "+" character, call the CHANGE
|
|
; function defined higher up in this code. This function allows the user
|
|
; to modify the current ELIZA script with the commands TYPE, SUBST,
|
|
; APPEND, ADD, START, RANK and DISPLA.
|
|
; After making any changes, return to the START label and carry on the
|
|
; conversation.
|
|
;
|
|
W'R TOP.(INPUT) .E. $*$, T'O NEWLST 000590
|
|
;
|
|
; If first word of the user input is a "*" character, goto the NEWLST label.
|
|
; NEWLST is defined later in this code. It inserts a new transformation rule,
|
|
; which the user will have given after the "*", into the current in-memory
|
|
; script and then returns to the START label to carry on the conversation.
|
|
;
|
|
S=SEQRDR.(INPUT) 000600
|
|
;
|
|
; Create the Slip sequence reader, S, for the user's INPUT list.
|
|
;
|
|
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
|
|
;
|
|
; Set the variable WORD to the next word in the user's INPUT list. Then
|
|
; test that word to see if it's a delimiter.
|
|
;
|
|
; Note that in Weizenbaum's 1966 CACM paper, only comma and period were
|
|
; listed as delimiters. And yet the example conversation given in that
|
|
; paper could not be reproduced unless BUT is also a delimiter.
|
|
;
|
|
; Note that WORD is a 36-bit integer. Weizenbaum developed ELIZA between
|
|
; 1964 and 1966 on an IBM 7094, which has a 36-bit word and uses a 6-bit
|
|
; character encoding. Characters were packed 6 to a word. In Slip, character
|
|
; strings longer than six characters are stored in successive list cells.
|
|
; In this case WORD=SEQLR.(S,F) is assigning the first six characters of
|
|
; the next word in the user's INPUT text to the integer variable WORD.
|
|
; If the word had fewer than six characters they would be left justified
|
|
; with space characters padding to the right.
|
|
;
|
|
;
|
|
; else {
|
|
; if WORD is one of the delimiters ".", "," or "BUT" {
|
|
; if we have found no keywords in the INPUT so far (IT .E. 0) {
|
|
; discard all words in INPUT to the left of, and including, this
|
|
; delimiter
|
|
; goto NOTYET and continue scanning the rest of the user INPUT
|
|
; for keywords
|
|
; }
|
|
; else {
|
|
; discard all words in INPUT to the right of, and including, this
|
|
; delimiter
|
|
; goto ENDTXT; scanning of the user INPUT is now complete
|
|
; }
|
|
; }
|
|
; }
|
|
;
|
|
W'R F .G. 0, T'O ENDTXT 000780
|
|
;
|
|
; If there were no more words to read in the user INPUT list, goto the
|
|
; ENDTXT label; scanning of the user INPUT is now complete.
|
|
; (F will be 1 when the sequence reader has traversed the whole INPUT
|
|
; list and is back at the list header.)
|
|
;
|
|
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
|
|
;
|
|
; Is WORD a keyword? Try to locate it in the KEY hashmap.
|
|
;
|
|
; Recall that more than one keyword may hash to the same entry in KEY,
|
|
; so each entry in KEY is a list that may have zero, one or many keyword
|
|
; transformation rules associated with it. We need to look through this
|
|
; list to see if it contains a keyword that exactly matches WORD.
|
|
;
|
|
; HASH the WORD to get the index I in the KEY table where this word
|
|
; would have been stored, if it is a keyword
|
|
; loop {
|
|
; try to read the next candidate list from the hashmap entry KEY(I)
|
|
; if there isn't another candidate list {
|
|
; WORD didn't match any entries so it's not a keyword
|
|
; goto NOTYET to continue scanning the user's input text
|
|
; }
|
|
; if WORD is the same as the first entry in this candidate list {
|
|
; WORD is a keyword and CAND is the transformation rule for
|
|
; this keyword, so goto KEYFND
|
|
; }
|
|
; }
|
|
;
|
|
KEYFND READER=TESTS.(CAND,S) 000860
|
|
W'R READER .E. 0, T'O NOTYET 000870
|
|
;
|
|
; Call the TESTS function, defined higher up in this code.
|
|
;
|
|
; TESTS checks that the whole keyword matches the whole user INPUT word. It
|
|
; also performs any keyword substitution in the user INPUT. (e.g. (MY = YOUR))
|
|
;
|
|
; If TESTS returns 0 it means the keyword is not identical to the word in
|
|
; the user input, so goto NOTYET to continue scanning the user INPUT.
|
|
;
|
|
; [This suggests that keywords must differ in the first six characters.
|
|
; (Because TESTS is called only for the first keyword candidate in
|
|
; the KEY hashmap that matches the first six characters of the user's
|
|
; input word).]
|
|
;
|
|
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
|
|
;
|
|
; Read the next element in the rules associated with this keyword.
|
|
; If we are back at the rules header, the rules list was empty, so goto
|
|
; NOTYET to continue scanning the user INPUT.
|
|
;
|
|
W'R IT .E. 0 .AND. FR .E. 0 001000
|
|
PLCKEY IT=READER 001010
|
|
KEYWRD=WORD 001020
|
|
;
|
|
; 001000 If this is the first keyword we've encountered in the user's INPUT
|
|
; (IT .E. 0), and the first entry in the associated rules is a list
|
|
; rather than a value (FR .E. 0)???, i.e. there is no precedence associated
|
|
; with this keyword, then record the associated rules reader in IT and
|
|
; the found keyword in KEYWRD. Then goto NOTYET (001100) to continue
|
|
; scanning the user's input.
|
|
;
|
|
OR W'R FR .L. 0 .AND. NEXT .G. PREDNC 001030
|
|
PREDNC=NEXT 001040
|
|
NEXT=SEQLR.(READER,FR) 001050
|
|
T'O PLCKEY 001060
|
|
0'E 001070
|
|
T'O NOTYET 001080
|
|
E'L 001090
|
|
T'O NOTYET 001100
|
|
R* * * * * * * * * * END OF MAJOR LOOP 001110
|
|
;
|
|
; 001030 Otherwise, if the first entry in the associated rules is a value???
|
|
; (FR .L. 0), i.e. the precedence of this keyword, and that value is greater
|
|
; than the precedence of the previously found highest precedence keyword
|
|
; (NEXT .G. PREDNC), then record the new highest precedence value in PREDNC
|
|
; and move the rule reader past the precedence value, then goto PLCKEY to
|
|
; also record the reader in IT and the found keyword in KEYWRD. Finally, goto
|
|
; NOTYET (001100) to continue scanning the user's input.
|
|
;
|
|
; Note that this differs from Weizenbaum's CACM paper, where it says that
|
|
; keywords of higher precedence are added to the top of a keyword stack and
|
|
; keywords of lower precedence are added to the bottom of this stack. This
|
|
; also means this code does not support the "NEWKEY" functionality he
|
|
; describes.
|
|
;
|
|
; [Note that this code implies that keywords in the script should never
|
|
; specify a precedence value of 0. If they do they would never be used
|
|
; (because NEXT .G. PREDNC will never be true).]
|
|
;
|
|
; 001080 Otherwise, ignore this keyword and return to NOTYET to continue
|
|
; scanning the user's INPUT.
|
|
;
|
|
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
|
|
;
|
|
; 001120 If IT is 0 it means we did not find any keywords in the user's
|
|
; input, so we cannot construct a response from the user's input combined
|
|
; with any of the transformation rules in the script.
|
|
;
|
|
; Instead we do one of two things: either print one of the memories we
|
|
; previously recorded in MYLIST, if any, or we use one of the messages
|
|
; from the NONE list (which is recorded in KEY(32)).
|
|
;
|
|
; 001130 This is the mysterious "when a certain counting mechanism is in a
|
|
; particular state": recall a memory only if the memory list (MYLIST) isn't
|
|
; empty and LIMIT happens to have the value 4.
|
|
;
|
|
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
|
|
;
|
|
; Otherwise, we did find a keyword (IT .E. 0 is false). If the keyword is
|
|
; the MEMORY keyword ("MY" in the DOCTOR script), then add a new memory to
|
|
; MYLIST before we carry on processing the transformation rules associated
|
|
; with the matched keyword.
|
|
;
|
|
; In the 1966 CACM paper, Weizenbaum says the selection of one of the
|
|
; transformations on the MEMORY list is random. The code shows that the
|
|
; selection is determined by the HASH value of the last word in the user's
|
|
; input. This means ELIZA conversations are repeatable, not random. If we
|
|
; have the HASH algorithm we should be able to reproduce the exact
|
|
; conversation. (The HASH function is part of the SLIP system.)
|
|
;
|
|
O'E 001270
|
|
SEQLL.(IT,FR) 001280
|
|
;
|
|
; Otherwise, the keyword we found isn't the MEMORY keyword, so just position
|
|
; the transformation rule sequence reader past the keyword and fall through
|
|
; to the matching code.
|
|
;
|
|
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
|
|
;
|
|
; If this keyword is a link to another keyword, switch to that keyword.
|
|
;
|
|
; An ELIZA script rule may have the form (HOW (=WHAT)). If the keyword
|
|
; HOW appears in the user's input and this transformation rule is selected,
|
|
; ELIZA will use the transformation rule associated with the keyword WHAT
|
|
; to generate its response.
|
|
;
|
|
; read the next decomposition rule from the selected transformation rule
|
|
; if the decomposition rule starts with an "=" symbol {
|
|
; assign the word after the "=" to WORD
|
|
; lookup WORD in the KEY hashmap
|
|
; if WORD doesn't exist in the KEY hashmap {
|
|
; (presumably this indicates a logical inconsistency in the script)
|
|
; goto one of the NOMATCH(1) .. NOMATCH(4) labels to print
|
|
; a message such as "HMMM" and back to the main conversation loop
|
|
; which NOMATCH label is selected is determined by the value LIMIT
|
|
; happens to have at this time
|
|
; }
|
|
; else {
|
|
; position IT at first decomposition rule for the linked keyword
|
|
; goto the TRY label to try to apply the decomposition rule
|
|
; }
|
|
; }
|
|
; else if there were no (or no more) decomposition rules (FR .G. 0) {
|
|
; (does this indicate an incorrectly formed script?)
|
|
; goto one of the NOMATCH(1) .. NOMATCH(4) labels
|
|
; }
|
|
;
|
|
TRY W'R YMATCH.(TOP.(ES),INPUT,MTLIST.(TEST)) .E. 0,T'O MATCH 001500
|
|
;
|
|
; Attempt to match the current decomposition rule (TOP.(ES)) to the user's
|
|
; INPUT.
|
|
;
|
|
; If it doesn't match (YMATCH returns 0), goto MATCH to try the next
|
|
; decomposition rule in the current transformation rule set.
|
|
;
|
|
; If it does match, the list TEST will contain the decomposed matching parts
|
|
; of the INPUT text ready for reassembly. E.g. ???
|
|
;
|
|
; The YMATCH function is part of the SLIP system.
|
|
;
|
|
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
|
|
0'E 001680
|
|
SUBST.(POINT+1,POINTR) 001690
|
|
T'O HIT 001700
|
|
E'L 001710
|
|
E'L 001720
|
|
;
|
|
; Select one of the reassembly rules associated with this decomposition rule.
|
|
;
|
|
; Reassembly rules are used in turn. This code adds a counter (001560) to
|
|
; the rules and uses it to record which reassembly rule was used last (001690).
|
|
; When all reassembly rules have been used (001620) the counter is returned
|
|
; to 1 (001660) and the first rule is used again.
|
|
;
|
|
HIT TXTPRT.(ASSMBL.(TRANS,TEST,MTLIST.(OUTPUT)),0) 001730
|
|
T'O START 001740
|
|
E'L 001750
|
|
;
|
|
; Finally, use the selected reassembly rule (TRANS) and list of decomposition
|
|
; parts (TEST) to assemble a response (in the list OUTPUT) and print it. Then
|
|
; goto the START label to await the next user input and continue the
|
|
; conversation.
|
|
;
|
|
; The ASSMBL function is part of the SLIP system.
|
|
;
|
|
; The E'L (END OF CONDITIONAL) on line 001750 closes the O'E (OTHERWISE)
|
|
; on line 001270. ???
|
|
;
|
|
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
|
|
|
|
|
|
|
|
|