source: trunk/example/mp2eliza.lisp

Last change on this file was 15, checked in by bmastenbrook, 20 years ago

Commit latest cliki bot

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 10.1 KB
Line 
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
Note: See TracBrowser for help on using the repository browser.