;;;; -*- Mode: Lisp; Syntax: Common-Lisp -*- ;;;; Code from Paradigms of AI Programming ;;;; Copyright (c) 1991 Peter Norvig ;;;; File prolog1.lisp: First version of the prolog interpreter (11.2). (requires "unify") ;; Clauses are represented as (head . body) cons cells (defun clause-head (clause) (first clause)) (defun clause-body (clause) (rest clause)) ;; Clauses are stored on the predicate's plist (defun get-clauses (pred) (get pred 'clauses)) (defun predicate (relation) (first relation)) (defvar *db-predicates* nil "A list of all predicates stored in the database.") (defmacro <- (&rest clause) "Add a clause to the data base." `(add-clause ',clause)) (defun add-clause (clause) "Add a clause to the data base, indexed by head's predicate." ;; The predicate must be a non-variable symbol. (let ((pred (predicate (clause-head clause)))) (assert (and (symbolp pred) (not (variable-p pred)))) (pushnew pred *db-predicates*) (setf (get pred 'clauses) (nconc (get-clauses pred) (list clause))) pred)) (defun clear-db () "Remove all clauses (for all predicates) from the data base." (mapc #'clear-predicate *db-predicates*)) (defun clear-predicate (predicate) "Remove the clauses for a single predicate." (setf (get predicate 'clauses) nil)) (defun prove (goal bindings) "Return a list of possible solutions to goal." (mapcan #'(lambda (clause) (let ((new-clause (rename-variables clause))) (prove-all (clause-body new-clause) (unify goal (clause-head new-clause) bindings)))) (get-clauses (predicate goal)))) (defun prove-all (goals bindings) "Return a list of solutions to the conjunction of goals." (cond ((eq bindings fail) fail) ((null goals) (list bindings)) (t (mapcan #'(lambda (goal1-solution) (prove-all (rest goals) goal1-solution)) (prove (first goals) bindings))))) (defun rename-variables (x) "Replace all variables in x with new ones." (sublis (mapcar #'(lambda (var) (cons var (gensym (string var)))) (variables-in x)) x)) (defun unique-find-anywhere-if (predicate tree &optional found-so-far) "Return a list of leaves of tree satisfying predicate, with duplicates removed." (if (atom tree) (if (funcall predicate tree) (adjoin tree found-so-far) found-so-far) (unique-find-anywhere-if predicate (first tree) (unique-find-anywhere-if predicate (rest tree) found-so-far)))) (defun find-anywhere-if (predicate tree) "Does predicate apply to any atom in the tree?" (if (atom tree) (funcall predicate tree) (or (find-anywhere-if predicate (first tree)) (find-anywhere-if predicate (rest tree))))) (defmacro ?- (&rest goals) `(top-level-prove ',goals)) (defun top-level-prove (goals) "Prove the goals, and print variables readably." (show-prolog-solutions (variables-in goals) (prove-all goals no-bindings))) (defun show-prolog-solutions (vars solutions) "Print the variables in each of the solutions." (if (null solutions) (format t "~&No.") (mapc #'(lambda (solution) (show-prolog-vars vars solution)) solutions)) (values)) (defun show-prolog-vars (vars bindings) "Print each variable with its binding." (if (null vars) (format t "~&Yes") (dolist (var vars) (format t "~&~a = ~a" var (subst-bindings bindings var)))) (princ ";")) (defun variables-in (exp) "Return a list of all the variables in EXP." (unique-find-anywhere-if #'variable-p exp))