1 | ;;;; -*- Mode: Lisp; Syntax: Common-Lisp -*- |
---|
2 | ;;;; Code from Paradigms of AI Programming |
---|
3 | ;;;; Copyright (c) 1991 Peter Norvig |
---|
4 | |
---|
5 | ;;;; File mp2eliza.lisp: Norvig's Eliza code for EECS 492 Machine Problem 2. |
---|
6 | ;;; assembled from auxfns.lisp, eliza.lisp, eliza1.lisp, patmatch.lisp, |
---|
7 | ;;; and eliza-pm.lisp |
---|
8 | |
---|
9 | ;;; auxfns |
---|
10 | |
---|
11 | (defpackage :eliza (:use :common-lisp) |
---|
12 | (:export :eliza)) |
---|
13 | (in-package :eliza) |
---|
14 | |
---|
15 | (defun starts-with (list x) |
---|
16 | "Is x a list whose first element is x?" |
---|
17 | (and (consp list) (eql (first list) x))) |
---|
18 | |
---|
19 | ;;; patmatch: Pattern matcher from section 6.2 |
---|
20 | |
---|
21 | (defconstant fail nil "Indicates pat-match failure") |
---|
22 | |
---|
23 | (defparameter no-bindings '((t . t)) |
---|
24 | "Indicates pat-match success, with no variables.") |
---|
25 | |
---|
26 | (defun pat-match (pattern input &optional (bindings no-bindings)) |
---|
27 | "Match pattern against input in the context of the bindings" |
---|
28 | (cond ((eq bindings fail) fail) |
---|
29 | ((variable-p pattern) |
---|
30 | (match-variable pattern input bindings)) |
---|
31 | ((eql pattern input) bindings) |
---|
32 | ((segment-pattern-p pattern) |
---|
33 | (segment-matcher pattern input bindings)) |
---|
34 | ((single-pattern-p pattern) ; *** |
---|
35 | (single-matcher pattern input bindings)) ; *** |
---|
36 | ((and (consp pattern) (consp input)) |
---|
37 | (pat-match (rest pattern) (rest input) |
---|
38 | (pat-match (first pattern) (first input) |
---|
39 | bindings))) |
---|
40 | (t fail))) |
---|
41 | |
---|
42 | (defun variable-p (x) |
---|
43 | "Is x a variable (a symbol beginning with `?')?" |
---|
44 | (and (symbolp x) (equal (char (symbol-name x) 0) #\?))) |
---|
45 | |
---|
46 | (defun get-binding (var bindings) |
---|
47 | "Find a (variable . value) pair in a binding list." |
---|
48 | (assoc var bindings)) |
---|
49 | |
---|
50 | (defun binding-var (binding) |
---|
51 | "Get the variable part of a single binding." |
---|
52 | (car binding)) |
---|
53 | |
---|
54 | (defun binding-val (binding) |
---|
55 | "Get the value part of a single binding." |
---|
56 | (cdr binding)) |
---|
57 | |
---|
58 | (defun make-binding (var val) (cons var val)) |
---|
59 | |
---|
60 | (defun lookup (var bindings) |
---|
61 | "Get the value part (for var) from a binding list." |
---|
62 | (binding-val (get-binding var bindings))) |
---|
63 | |
---|
64 | (defun extend-bindings (var val bindings) |
---|
65 | "Add a (var . value) pair to a binding list." |
---|
66 | (cons (make-binding var val) |
---|
67 | ;; Once we add a "real" binding, |
---|
68 | ;; we can get rid of the dummy no-bindings |
---|
69 | (if (eq bindings no-bindings) |
---|
70 | nil |
---|
71 | bindings))) |
---|
72 | |
---|
73 | (defun match-variable (var input bindings) |
---|
74 | "Does VAR match input? Uses (or updates) and returns bindings." |
---|
75 | (let ((binding (get-binding var bindings))) |
---|
76 | (cond ((not binding) (extend-bindings var input bindings)) |
---|
77 | ((equal input (binding-val binding)) bindings) |
---|
78 | (t fail)))) |
---|
79 | |
---|
80 | (setf (get '?is 'single-match) 'match-is) |
---|
81 | (setf (get '?or 'single-match) 'match-or) |
---|
82 | (setf (get '?and 'single-match) 'match-and) |
---|
83 | (setf (get '?not 'single-match) 'match-not) |
---|
84 | |
---|
85 | (setf (get '?* 'segment-match) 'segment-match) |
---|
86 | (setf (get '?+ 'segment-match) 'segment-match+) |
---|
87 | (setf (get '?? 'segment-match) 'segment-match?) |
---|
88 | (setf (get '?if 'segment-match) 'match-if) |
---|
89 | |
---|
90 | (defun segment-pattern-p (pattern) |
---|
91 | "Is this a segment-matching pattern like ((?* var) . pat)?" |
---|
92 | (and (consp pattern) (consp (first pattern)) |
---|
93 | (symbolp (first (first pattern))) |
---|
94 | (segment-match-fn (first (first pattern))))) |
---|
95 | |
---|
96 | (defun single-pattern-p (pattern) |
---|
97 | "Is this a single-matching pattern? |
---|
98 | E.g. (?is x predicate) (?and . patterns) (?or . patterns)." |
---|
99 | (and (consp pattern) |
---|
100 | (single-match-fn (first pattern)))) |
---|
101 | |
---|
102 | (defun segment-matcher (pattern input bindings) |
---|
103 | "Call the right function for this kind of segment pattern." |
---|
104 | (funcall (segment-match-fn (first (first pattern))) |
---|
105 | pattern input bindings)) |
---|
106 | |
---|
107 | (defun single-matcher (pattern input bindings) |
---|
108 | "Call the right function for this kind of single pattern." |
---|
109 | (funcall (single-match-fn (first pattern)) |
---|
110 | (rest pattern) input bindings)) |
---|
111 | |
---|
112 | (defun segment-match-fn (x) |
---|
113 | "Get the segment-match function for x, |
---|
114 | if it is a symbol that has one." |
---|
115 | (when (symbolp x) (get x 'segment-match))) |
---|
116 | |
---|
117 | (defun single-match-fn (x) |
---|
118 | "Get the single-match function for x, |
---|
119 | if it is a symbol that has one." |
---|
120 | (when (symbolp x) (get x 'single-match))) |
---|
121 | |
---|
122 | (defun match-is (var-and-pred input bindings) |
---|
123 | "Succeed and bind var if the input satisfies pred, |
---|
124 | where var-and-pred is the list (var pred)." |
---|
125 | (let* ((var (first var-and-pred)) |
---|
126 | (pred (second var-and-pred)) |
---|
127 | (new-bindings (pat-match var input bindings))) |
---|
128 | (if (or (eq new-bindings fail) |
---|
129 | (not (funcall pred input))) |
---|
130 | fail |
---|
131 | new-bindings))) |
---|
132 | |
---|
133 | (defun match-and (patterns input bindings) |
---|
134 | "Succeed if all the patterns match the input." |
---|
135 | (cond ((eq bindings fail) fail) |
---|
136 | ((null patterns) bindings) |
---|
137 | (t (match-and (rest patterns) input |
---|
138 | (pat-match (first patterns) input |
---|
139 | bindings))))) |
---|
140 | |
---|
141 | (defun match-or (patterns input bindings) |
---|
142 | "Succeed if any one of the patterns match the input." |
---|
143 | (if (null patterns) |
---|
144 | fail |
---|
145 | (let ((new-bindings (pat-match (first patterns) |
---|
146 | input bindings))) |
---|
147 | (if (eq new-bindings fail) |
---|
148 | (match-or (rest patterns) input bindings) |
---|
149 | new-bindings)))) |
---|
150 | |
---|
151 | (defun match-not (patterns input bindings) |
---|
152 | "Succeed if none of the patterns match the input. |
---|
153 | This will never bind any variables." |
---|
154 | (if (match-or patterns input bindings) |
---|
155 | fail |
---|
156 | bindings)) |
---|
157 | |
---|
158 | (defun segment-match (pattern input bindings &optional (start 0)) |
---|
159 | "Match the segment pattern ((?* var) . pat) against input." |
---|
160 | (let ((var (second (first pattern))) |
---|
161 | (pat (rest pattern))) |
---|
162 | (if (null pat) |
---|
163 | (match-variable var input bindings) |
---|
164 | (let ((pos (first-match-pos (first pat) input start))) |
---|
165 | (if (null pos) |
---|
166 | fail |
---|
167 | (let ((b2 (pat-match |
---|
168 | pat (subseq input pos) |
---|
169 | (match-variable var (subseq input 0 pos) |
---|
170 | bindings)))) |
---|
171 | ;; If this match failed, try another longer one |
---|
172 | (if (eq b2 fail) |
---|
173 | (segment-match pattern input bindings (+ pos 1)) |
---|
174 | b2))))))) |
---|
175 | |
---|
176 | (defun first-match-pos (pat1 input start) |
---|
177 | "Find the first position that pat1 could possibly match input, |
---|
178 | starting at position start. If pat1 is non-constant, then just |
---|
179 | return start." |
---|
180 | (cond ((and (atom pat1) (not (variable-p pat1))) |
---|
181 | (position pat1 input :start start :test #'equal)) |
---|
182 | ((<= start (length input)) start) ;*** fix, rjf 10/1/92 (was <) |
---|
183 | (t nil))) |
---|
184 | |
---|
185 | (defun segment-match+ (pattern input bindings) |
---|
186 | "Match one or more elements of input." |
---|
187 | (segment-match pattern input bindings 1)) |
---|
188 | |
---|
189 | (defun segment-match? (pattern input bindings) |
---|
190 | "Match zero or one element of input." |
---|
191 | (let ((var (second (first pattern))) |
---|
192 | (pat (rest pattern))) |
---|
193 | (or (pat-match (cons var pat) input bindings) |
---|
194 | (pat-match pat input bindings)))) |
---|
195 | |
---|
196 | (defun match-if (pattern input bindings) |
---|
197 | "Test an arbitrary expression involving variables. |
---|
198 | The pattern looks like ((?if code) . rest)." |
---|
199 | ;; *** fix, rjf 10/1/92 (used to eval binding values) |
---|
200 | (and (progv (mapcar #'car bindings) |
---|
201 | (mapcar #'cdr bindings) |
---|
202 | (eval (second (first pattern)))) |
---|
203 | (pat-match (rest pattern) input bindings))) |
---|
204 | |
---|
205 | (defun pat-match-abbrev (symbol expansion) |
---|
206 | "Define symbol as a macro standing for a pat-match pattern." |
---|
207 | (setf (get symbol 'expand-pat-match-abbrev) |
---|
208 | (expand-pat-match-abbrev expansion))) |
---|
209 | |
---|
210 | (defun expand-pat-match-abbrev (pat) |
---|
211 | "Expand out all pattern matching abbreviations in pat." |
---|
212 | (cond ((and (symbolp pat) (get pat 'expand-pat-match-abbrev))) |
---|
213 | ((atom pat) pat) |
---|
214 | (t (cons (expand-pat-match-abbrev (first pat)) |
---|
215 | (expand-pat-match-abbrev (rest pat)))))) |
---|
216 | |
---|
217 | (defun rule-based-translator |
---|
218 | (input rules &key (matcher #'pat-match) |
---|
219 | (rule-if #'first) (rule-then #'rest) (action #'sublis)) |
---|
220 | "Find the first rule in rules that matches input, |
---|
221 | and apply the action to that rule." |
---|
222 | (some |
---|
223 | #'(lambda (rule) |
---|
224 | (let ((result (funcall matcher (funcall rule-if rule) |
---|
225 | input))) |
---|
226 | (if (not (eq result fail)) |
---|
227 | (funcall action result (funcall rule-then rule))))) |
---|
228 | rules)) |
---|
229 | |
---|
230 | ;;;; |
---|
231 | ;;;; eliza1: Basic version of the Eliza program |
---|
232 | |
---|
233 | ;;; ============================== |
---|
234 | |
---|
235 | (defun rule-pattern (rule) (first rule)) |
---|
236 | (defun rule-responses (rule) (rest rule)) |
---|
237 | |
---|
238 | (defvar *viewpoint* nil) |
---|
239 | |
---|
240 | (defun switch-viewpoint (words) |
---|
241 | "Change I to you and vice versa, and so on." |
---|
242 | (sublis *viewpoint* |
---|
243 | words)) |
---|
244 | |
---|
245 | ;;; ============================== |
---|
246 | |
---|
247 | (defun flatten (the-list) |
---|
248 | "Append together elements (or lists) in the list." |
---|
249 | (mappend #'mklist the-list)) |
---|
250 | |
---|
251 | (defun mklist (x) |
---|
252 | "Return x if it is a list, otherwise (x)." |
---|
253 | (if (listp x) |
---|
254 | x |
---|
255 | (list x))) |
---|
256 | |
---|
257 | (defun mappend (fn the-list) |
---|
258 | "Apply fn to each element of list and append the results." |
---|
259 | (apply #'append (mapcar fn the-list))) |
---|
260 | |
---|
261 | (defun random-elt (choices) |
---|
262 | "Choose an element from a list at random." |
---|
263 | (elt choices (random (length choices)))) |
---|
264 | |
---|
265 | ;;;; |
---|
266 | ;;;; eliza: Advanced version of Eliza. |
---|
267 | ;;; Has more rules, and accepts input without parens. |
---|
268 | |
---|
269 | (defun read-line-no-punct (line) |
---|
270 | "Read an input line, ignoring punctuation." |
---|
271 | (let ((*read-eval* nil) |
---|
272 | (*package* (find-package :eliza))) |
---|
273 | (read-from-string |
---|
274 | (concatenate 'string "(" (substitute-if #\space #'punctuation-p |
---|
275 | line) |
---|
276 | ")")))) |
---|
277 | |
---|
278 | (defun punctuation-p (char) (find char ".,;:`'!?#-()\\\"")) |
---|
279 | |
---|
280 | ;;; ============================== |
---|
281 | |
---|
282 | (defun eliza (line) |
---|
283 | "Respond to user input using pattern matching rules." |
---|
284 | (let* ((input (read-line-no-punct line)) |
---|
285 | (response (flatten (use-eliza-rules input)))) |
---|
286 | (print-with-spaces response))) |
---|
287 | |
---|
288 | (defun print-with-spaces (list) |
---|
289 | (let ((*print-case* :downcase)) |
---|
290 | (format nil "~{~A ~}" list))) |
---|
291 | |
---|
292 | ;;; ============================== |
---|
293 | |
---|
294 | |
---|
295 | (defvar *eliza-rules* nil) |
---|
296 | ;;; ============================== |
---|
297 | |
---|
298 | ;;;; eliza-pm: use advanced pattern matcher |
---|
299 | |
---|
300 | (defun use-eliza-rules (input) |
---|
301 | "Find some rule with which to transform the input." |
---|
302 | (rule-based-translator input *eliza-rules* |
---|
303 | :action #'(lambda (bindings responses) |
---|
304 | (sublis (switch-viewpoint bindings) |
---|
305 | (random-elt responses))))) |
---|
306 | |
---|