;;  MATCH.L for PC-LISP.EXE (V2.13)
;;  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
;;  A DEDUCTIVE DATA BASE RETRIEVER AS PER LISPcraft CHAPTERS 21&22
;;  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~  
;;        This file called match.l implements all of the functions in  
;;  chapters 21 and 22 of LISPcraft by R.Wilensky. Together they form     
;;  a deductive data base retriever with two access functions. One is   
;;  called (insert) and the other (retrieve). Insert takes implications
;;  and base cases and inserts them into the given data base. (retrieve)
;;  returns a list of matches made with the data base and any bindings
;;  neccssary to make the match true. Hence an output like (nil) means
;;  one match requiring no bindings. The functions have been slightly
;;  modified to run with PC-LISP.  Note that they require the PC-LISP.L
;;  file to be loaded specificially for the let macro and a few other
;;  goodies. If you put PC-LISP.L in the current directory it will be
;;  automatically loaded. Or you can put it in a library directory, see
;;  the (load) function.
;;
;;             August 22nd 1986  
;;                Peter Ashwood-Smith
;;
;;  Example queries:
;;            (mammal Fido)     gives (nil) meaning Yes he is a mammal
;;            (dog ?x)          gives (?x Fido) meaning Yes if (?x is Fido)
;;            (mammal ?x)       etc.. you get the idea.
;;            (? Fido)
;;
;;      You really cannot get much out of this example unless you get
;; the LISPcraft book. Have Fun!
  
;;
;; Main processing Loop - input a data base query, expand the variables
;; ?x to (*var* x) as the read macro in LISPcraft page 295 would do then 
;; pass the request to the (retrieve) function.
;;

(setsyntax '|?| 'vmacro '(lambda()(list '*var* (read))))

(setq displace-macros t)   ;runs much faster if let is displaced at eval time

(defun ProcessQueries (data-base)
       (prog (InputQuery)
	loop (princ "query?")
	     (setq InputQuery (read))   
	     (cond ((null InputQuery) (return)))
	     (princ "ans=")
	     (patom (CompressVariables (retrieve InputQuery data-base)))
	     (princ (ascii 10))   
	     (go loop)
       )
)

;;
;; Opposite of Read Macro for ? - turn list  elements like (*var* x) into
;; ?x
;;

(defun CompressVariables (List)
       (cond ((null List) ())
	     ((atom List) List)
	     ((eq (car List) '*var*)
		(implode (list '|?| (cadr List)))
	     )
	     (t (cons(CompressVariables(car List))(CompressVariables (cdr List))))
       )
)

;;
;; top level matcher function, just drives the recursive next level
;; by setting bindings to nil.
;;

(defun match (pattern1 pattern2) 
      (match-with-bindings pattern1 pattern2 nil)
)

(defun match-with-bindings (pattern1 pattern2 bindings)
      (cond ((pattern-var-p pattern1)
	       (variable-match pattern1 pattern2 bindings)
	    )
	    ((pattern-var-p pattern2)
	       (variable-match pattern2 pattern1 bindings)
	    )
	    ((atom pattern1)
	       (cond ((eq pattern1 pattern2) 
			 (list bindings)
		     )
	       )
	    )
	    ((atom pattern2) nil)
	    (t (let ((car-result    
		       (match-with-bindings
			 (car pattern1)(car pattern2) bindings)))     
		    (and car-result
		       (match-with-bindings
			 (cdr pattern1)
			 (cdr pattern2)
			 (car car-result)
		       )
		    )
	       )  
	    )
      )   
)

(defun variable-match (pattern-var item bindings)
       (cond ((equal pattern-var item) (list bindings))
	     (t (let ((var-binding (get-binding pattern-var bindings)))
		  (cond (var-binding
			  (match-with-bindings var-binding item bindings))
			((not (contained-in pattern-var item bindings))
			  (list (add-binding pattern-var item bindings)))
		  )
		)
	     )
       )
)

(defun contained-in (pattern-var item bindings)
      (cond ((atom item) nil)
	    ((pattern-var-p item)
	      (or (equal pattern-var item)
		  (contained-in pattern-var 
			       (get-binding item bindings)
				bindings)
	      )        
	    )
	    (t (or (contained-in pattern-var (car item) bindings)
		   (contained-in pattern-var (cdr item) bindings)
	       )
	    )         
      )
)

(defun add-binding (pattern-var item bindings)
       (cons (list pattern-var item) bindings)
)

(defun get-binding (pattern-var bindings)
       (cadr (assoc pattern-var bindings))
) 

(defun pattern-var-p (item)
       (and (listp item) (eq '*var* (car item)))
)

;; 
;; Fast Data Base Manager Operations. Using matcher function above to perform
;; deductive retreival. Indexing as per LISPcraft chapter 22.
;;

(defun replace-variables(item)
      (let ((!bindings ()))
	   (replace-variables-with-bindings item)))

(defun replace-variables-with-bindings(item)
      (cond ((atom item) item)
	    ((pattern-var-p item)
	     (let ((var-binding (get-binding item !bindings)))
		  (cond (var-binding)
			(t (let ((newvar (makevar (gensym 'var))))
				(setq !bindings
				   (add-binding item newvar !bindings))
			    newvar))
		  )
	     )
	    )
	    (t (cons (replace-variables-with-bindings (car item))
		     (replace-variables-with-bindings (cdr item))
	       )
	    )
      )
)

(defun makevar (atom)
       (list '*var* atom)
)

(defun query (request data-base)
       (apply 'append (mapcar '(lambda(item)(match item request))  
			      data-base
		      )
       )
)
   
(defun index (item data-base)
      (let ((place (cond ((atom (car item)) (car item))
			 ((pattern-var-p (car item)) '*var*)
			 (t '*list*)
		   )   
	    )
	   )
	   (putprop place (cons (replace-variables item)(get place data-base))
				data-base)
	   (putprop data-base
		   (enter place (get data-base '*keys*)) 
		  '*keys*) 
      )
)
  
(defun enter (e l)
      (cond ((not (memq e l)) (cons e l))
	    (t l)
      )
)

(defun fast-query (request data-base)
      (cond ((pattern-var-p (car request))
	     (apply 'append  
		(mapcar '(lambda(key)(query request (get key data-base)))
			 (get data-base '*keys*)
		)
	     )
	    )
	    (t (append
		 (query request (get (cond ((atom (car request))
					      (car request)
					   )
					   (t '*list*)
				      )
				      data-base
				)
		 )
		 (query request (get '*var* data-base))
	       )
	    )
      )
)   

;;
;; deductive retreiver (LISPcraft page 314) use backward chaining to establish
;; bindings.
;;

(defun retrieve (request data-base)
      (append
	  (fast-query request data-base)
	  (apply 'append  
	       (mapcar '(lambda(bindings)
			    (retrieve
				(substitute-vars
				   (get-binding '(*var* antecedent) bindings)
				   bindings)
				data-base))
			(fast-query (list '<- request '(*var* antecedent))
				    data-base)
		)
	  )
      )
)

;;
;; substitute variables for bindings recursively. LISPcraft page 315.
;;

(defun substitute-vars (item bindings)
      (cond ((atom item) item)
	    ((pattern-var-p item)
	       (let ((binding (get-binding item bindings)))
		    (cond (binding (substitute-vars binding bindings))
			  (t item)
		    )
	       )
	    )
	    (t (cons (substitute-vars (car item) bindings)
		     (substitute-vars (cdr item) bindings)
	       )
	    )
      )
)

;;
;; page 315 of LISPcraft add too !d-b1!
;; by calling index to insert the implications and base cases.
;;

(index  '(<- (scales ?x) (fish ?x)) '!d-b1!)       ; fishes have scales
(index  '(<- (fins ?x) (fish ?x)) '!d-b1!)         ; fishes have fins
(index  '(<- (legs ?x) (mammal ?x)) '!d-b1!)       ; some mammals have legs
(index  '(<- (mammal ?x) (dog ?x)) '!d-b1!)        ; a dog is a mammal
(index  '(<- (dog ?x) (poodle ?x)) '!d-b1!)        ; a poodle is a dog
(index  '(poodle Fido) '!d-b1!)                    ; fido is a poodle
(index  '(horse Terry) '!d-b1!)                    ; terry is a horse
(index  '(fish Eric) '!d-b1!)                      ; Eric is a fish

;;
;; start processing queries from data base #1 which was entered above
;; some good things to try are (mammal Fido) which will return (nil)
;; meaning that one match was found needing no bindings to make it true.
;; this was established via the chain (poodle Fido)-->(dog Fido)-->
;; (mammal Fido).
;;

(defun run() (ProcessQueries '!d-b1!))

(princ "Data Base Retreiver Loaded and Ready To Go")
(princ (ascii 10))
(princ "Just type (run) to start it, have fun.")
(princ (ascii 10))
