Component mp2eliza

You are here: All Systems / cliki-bot / mp2eliza

;;;; -*- Mode: Lisp; Syntax: Common-Lisp -*-
;;;; Code from Paradigms of AI Programming
;;;; Copyright (c) 1991 Peter Norvig

;;;; File mp2eliza.lisp: Norvig's Eliza code for EECS 492 Machine Problem 2.
;;;    assembled from auxfns.lisp, eliza.lisp, eliza1.lisp, patmatch.lisp,
;;;    and eliza-pm.lisp

;;; auxfns

(defpackage :eliza (:use :common-lisp)
            (:export :eliza)
)

(in-package :eliza)

(defun starts-with (list x)
    "Is x a list whose first element is x?"
    (and (consp list) (eql (first list) x))
)


;;; patmatch: Pattern matcher from section 6.2

(defconstant fail nil "Indicates pat-match failure")

(defparameter no-bindings '((t . t))
  "Indicates pat-match success, with no variables."
)


(defun pat-match (pattern input &optional (bindings no-bindings))
  "Match pattern against input in the context of the bindings"
  (cond ((eq bindings fail) fail)
        ((variable-p pattern)
         (match-variable pattern input bindings)
)

        ((eql pattern input) bindings)
        ((segment-pattern-p pattern)                
         (segment-matcher pattern input bindings)
)
 
        ((single-pattern-p pattern)                 ; ***
        (single-matcher pattern input bindings)
)
  ; ***
       ((and (consp pattern) (consp input))
         (pat-match (rest pattern) (rest input)
                    (pat-match (first pattern) (first input)
                               bindings
)
)
)

        (t fail)
)
)


(defun variable-p (x)
  "Is x a variable (a symbol beginning with `?')?"
  (and (symbolp x) (equal (char (symbol-name x) 0) #\?))
)


(defun get-binding (var bindings)
  "Find a (variable . value) pair in a binding list."
  (assoc var bindings)
)


(defun binding-var (binding)
  "Get the variable part of a single binding."
  (car binding)
)


(defun binding-val (binding)
  "Get the value part of a single binding."
  (cdr binding)
)


(defun make-binding (var val) (cons var val))

(defun lookup (var bindings)
  "Get the value part (for var) from a binding list."
  (binding-val (get-binding var bindings))
)


(defun extend-bindings (var val bindings)
  "Add a (var . value) pair to a binding list."
  (cons (make-binding var val)
        ;; Once we add a "real" binding,
       ;; we can get rid of the dummy no-bindings
       (if (eq bindings no-bindings)
            nil
            bindings
)
)
)


(defun match-variable (var input bindings)
  "Does VAR match input?  Uses (or updates) and returns bindings."
  (let ((binding (get-binding var bindings)))
    (cond ((not binding) (extend-bindings var input bindings))
          ((equal input (binding-val binding)) bindings)
          (t fail)
)
)
)


(setf (get '?is  'single-match) 'match-is)
(setf (get '?or  'single-match) 'match-or)
(setf (get '?and 'single-match) 'match-and)
(setf (get '?not 'single-match) 'match-not)

(setf (get '?*  'segment-match) 'segment-match)
(setf (get '?+  'segment-match) 'segment-match+)
(setf (get '??  'segment-match) 'segment-match?)
(setf (get '?if 'segment-match) 'match-if)

(defun segment-pattern-p (pattern)
  "Is this a segment-matching pattern like ((?* var) . pat)?"
  (and (consp pattern) (consp (first pattern))
       (symbolp (first (first pattern)))
       (segment-match-fn (first (first pattern)))
)
)


(defun single-pattern-p (pattern)
  "Is this a single-matching pattern?
  E.g. (?is x predicate) (?and . patterns) (?or . patterns)."

  (and (consp pattern)
       (single-match-fn (first pattern))
)
)


(defun segment-matcher (pattern input bindings)
  "Call the right function for this kind of segment pattern."
  (funcall (segment-match-fn (first (first pattern)))
           pattern input bindings
)
)


(defun single-matcher (pattern input bindings)
  "Call the right function for this kind of single pattern."
  (funcall (single-match-fn (first pattern))
           (rest pattern) input bindings
)
)


(defun segment-match-fn (x)
  "Get the segment-match function for x,
  if it is a symbol that has one."

  (when (symbolp x) (get x 'segment-match))
)


(defun single-match-fn (x)
  "Get the single-match function for x,
  if it is a symbol that has one."

  (when (symbolp x) (get x 'single-match))
)


(defun match-is (var-and-pred input bindings)
  "Succeed and bind var if the input satisfies pred,
  where var-and-pred is the list (var pred)."

  (let* ((var (first var-and-pred))
         (pred (second var-and-pred))
         (new-bindings (pat-match var input bindings))
)

    (if (or (eq new-bindings fail)
            (not (funcall pred input))
)

        fail
        new-bindings
)
)
)


(defun match-and (patterns input bindings)
  "Succeed if all the patterns match the input."
  (cond ((eq bindings fail) fail)
        ((null patterns) bindings)
        (t (match-and (rest patterns) input
                      (pat-match (first patterns) input
                                 bindings
)
)
)
)
)


(defun match-or (patterns input bindings)
  "Succeed if any one of the patterns match the input."
  (if (null patterns)
      fail
      (let ((new-bindings (pat-match (first patterns)
                                     input bindings
)
)
)

        (if (eq new-bindings fail)
            (match-or (rest patterns) input bindings)
            new-bindings
)
)
)
)


(defun match-not (patterns input bindings)
  "Succeed if none of the patterns match the input.
  This will never bind any variables."

  (if (match-or patterns input bindings)
      fail
      bindings
)
)


(defun segment-match (pattern input bindings &optional (start 0))
  "Match the segment pattern ((?* var) . pat) against input."
  (let ((var (second (first pattern)))
        (pat (rest pattern))
)

    (if (null pat)
        (match-variable var input bindings)
        (let ((pos (first-match-pos (first pat) input start)))
          (if (null pos)
              fail
              (let ((b2 (pat-match
                          pat (subseq input pos)
                          (match-variable var (subseq input 0 pos)
                                          bindings
)
)
)
)

                ;; If this match failed, try another longer one
               (if (eq b2 fail)
                    (segment-match pattern input bindings (+ pos 1))
                    b2
)
)
)
)
)
)
)


(defun first-match-pos (pat1 input start)
  "Find the first position that pat1 could possibly match input,
  starting at position start.  If pat1 is non-constant, then just
  return start."

  (cond ((and (atom pat1) (not (variable-p pat1)))
         (position pat1 input :start start :test #'equal)
)

        ((<= start (length input)) start) ;*** fix, rjf 10/1/92 (was <)
       (t nil)
)
)


(defun segment-match+ (pattern input bindings)
  "Match one or more elements of input."
  (segment-match pattern input bindings 1)
)


(defun segment-match? (pattern input bindings)
  "Match zero or one element of input."
  (let ((var (second (first pattern)))
        (pat (rest pattern))
)

    (or (pat-match (cons var pat) input bindings)
        (pat-match pat input bindings)
)
)
)


(defun match-if (pattern input bindings)
  "Test an arbitrary expression involving variables.
  The pattern looks like ((?if code) . rest)."

  ;; *** fix, rjf 10/1/92 (used to eval binding values)
 (and (progv (mapcar #'car bindings)
              (mapcar #'cdr bindings)
          (eval (second (first pattern)))
)

       (pat-match (rest pattern) input bindings)
)
)


(defun pat-match-abbrev (symbol expansion)
  "Define symbol as a macro standing for a pat-match pattern."
  (setf (get symbol 'expand-pat-match-abbrev)
    (expand-pat-match-abbrev expansion)
)
)


(defun expand-pat-match-abbrev (pat)
  "Expand out all pattern matching abbreviations in pat."
  (cond ((and (symbolp pat) (get pat 'expand-pat-match-abbrev)))
        ((atom pat) pat)
        (t (cons (expand-pat-match-abbrev (first pat))
                 (expand-pat-match-abbrev (rest pat))
)
)
)
)


(defun rule-based-translator
       (input rules &key (matcher #'pat-match)
        (rule-if #'first) (rule-then #'rest) (action #'sublis)
)

  "Find the first rule in rules that matches input,
  and apply the action to that rule."

  (some
    #'(lambda (rule)
        (let ((result (funcall matcher (funcall rule-if rule)
                               input
)
)
)

          (if (not (eq result fail))
              (funcall action result (funcall rule-then rule))
)
)
)

    rules
)
)


;;;;
;;;;  eliza1: Basic version of the Eliza program

;;; ==============================

(defun rule-pattern (rule) (first rule))
(defun rule-responses (rule) (rest rule))

(defvar *viewpoint* nil)

(defun switch-viewpoint (words)
  "Change I to you and vice versa, and so on."
  (sublis *viewpoint*
          words
)
)


;;; ==============================

(defun flatten (the-list)
  "Append together elements (or lists) in the list."
  (mappend #'mklist the-list)
)


(defun mklist (x)
  "Return x if it is a list, otherwise (x)."
  (if (listp x)
      x
      (list x)
)
)


(defun mappend (fn the-list)        
  "Apply fn to each element of list and append the results."
  (apply #'append (mapcar fn the-list))
)


(defun random-elt (choices)
  "Choose an element from a list at random."
  (elt choices (random (length choices)))
)


;;;;
;;;;  eliza: Advanced version of Eliza.
;;; Has more rules, and accepts input without parens.

(defun read-line-no-punct (line)
  "Read an input line, ignoring punctuation."
  (let ((*read-eval* nil)
        (*package* (find-package :eliza))
)

    (read-from-string
     (concatenate 'string "(" (substitute-if #\space #'punctuation-p
                                            line
)

                 ")"
)
)
)
)


(defun punctuation-p (char) (find char ".,;:`'!?#-()\\\""))

;;; ==============================

(defun eliza (line)
  "Respond to user input using pattern matching rules."
  (let* ((input (read-line-no-punct line))
         (response (flatten (use-eliza-rules input)))
)

    (print-with-spaces response)
)
)


(defun print-with-spaces (list)
  (let ((*print-case* :downcase))
    (format nil "~{~A ~}" list)
)
)


;;; ==============================


(defvar *eliza-rules* nil)
;;; ==============================

;;;; eliza-pm: use advanced pattern matcher

(defun use-eliza-rules (input)
  "Find some rule with which to transform the input."
  (rule-based-translator input *eliza-rules*   
    :action #'(lambda (bindings responses)
                (sublis (switch-viewpoint bindings)
                        (random-elt responses)
)
)
)
)


Lisppaste pastes can be made by anyone at any time. Imagine a fearsomely comprehensive disclaimer of liability. Now fear, comprehensively.