forked from norvig/paip-lisp
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathprolog.lisp
139 lines (116 loc) · 4.45 KB
/
prolog.lisp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
;;;; -*- Mode: Lisp; Syntax: Common-Lisp -*-
;;;; Code from Paradigms of AI Programming
;;;; Copyright (c) 1991 Peter Norvig
;;;; File prolog.lisp: prolog from (11.3), with interactive backtracking.
(requires "unify") ; does not require "prolog1"
;;;; does not include destructive unification (11.6); see prologc.lisp
;; 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))
(defun args (x) "The arguments of a relation" (rest x))
(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 ',(replace-?-vars 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 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 ',(replace-?-vars goals)))
(defun prove-all (goals bindings)
"Find a solution to the conjunction of goals."
(cond ((eq bindings fail) fail)
((null goals) bindings)
(t (prove (first goals) bindings (rest goals)))))
(defun prove (goal bindings other-goals)
"Return a list of possible solutions to goal."
(let ((clauses (get-clauses (predicate goal))))
(if (listp clauses)
(some
#'(lambda (clause)
(let ((new-clause (rename-variables clause)))
(prove-all
(append (clause-body new-clause) other-goals)
(unify goal (clause-head new-clause) bindings))))
clauses)
;; The predicate's "clauses" can be an atom:
;; a primitive function to call
(funcall clauses (rest goal) bindings
other-goals))))
(defun top-level-prove (goals)
(prove-all `(,@goals (show-prolog-vars ,@(variables-in goals)))
no-bindings)
(format t "~&No.")
(values))
(defun show-prolog-vars (vars bindings other-goals)
"Print each variable with its binding.
Then ask the user if more solutions are desired."
(if (null vars)
(format t "~&Yes")
(dolist (var vars)
(format t "~&~a = ~a" var
(subst-bindings bindings var))))
(if (continue-p)
fail
(prove-all other-goals bindings)))
(setf (get 'show-prolog-vars 'clauses) 'show-prolog-vars)
(defun continue-p ()
"Ask user if we should continue looking for solutions."
(case (read-char)
(#\; t)
(#\. nil)
(#\newline (continue-p))
(otherwise
(format t " Type ; to see more or . to stop")
(continue-p))))
(defun variables-in (exp)
"Return a list of all the variables in EXP."
(unique-find-anywhere-if #'non-anon-variable-p exp))
(defun non-anon-variable-p (x)
(and (variable-p x) (not (eq x '?))))
(defun replace-?-vars (exp)
"Replace any ? within exp with a var of the form ?123."
(cond ((eq exp '?) (gensym "?"))
((atom exp) exp)
(t (reuse-cons (replace-?-vars (first exp))
(replace-?-vars (rest exp))
exp))))