0.pre8.54
[sbcl.git] / src / pcl / cpl.lisp
1 ;;;; This software is part of the SBCL system. See the README file for
2 ;;;; more information.
3
4 ;;;; This software is derived from software originally released by Xerox
5 ;;;; Corporation. Copyright and release statements follow. Later modifications
6 ;;;; to the software are in the public domain and are provided with
7 ;;;; absolutely no warranty. See the COPYING and CREDITS files for more
8 ;;;; information.
9
10 ;;;; copyright information from original PCL sources:
11 ;;;;
12 ;;;; Copyright (c) 1985, 1986, 1987, 1988, 1989, 1990 Xerox Corporation.
13 ;;;; All rights reserved.
14 ;;;;
15 ;;;; Use and copying of this software and preparation of derivative works based
16 ;;;; upon this software are permitted. Any distribution of this software or
17 ;;;; derivative works must comply with all applicable United States export
18 ;;;; control laws.
19 ;;;;
20 ;;;; This software is made available AS IS, and Xerox Corporation makes no
21 ;;;; warranty about the software, its performance or its conformity to any
22 ;;;; specification.
23
24 (in-package "SB-PCL")
25 \f
26 ;;;; COMPUTE-CLASS-PRECEDENCE-LIST and friends
27
28 ;;; Knuth section 2.2.3 has some interesting notes on this.
29 ;;;
30 ;;; What appears here is basically the algorithm presented there.
31 ;;;
32 ;;; The key idea is that we use class-precedence-description (CPD) structures
33 ;;; to store the precedence information as we proceed. The CPD structure for
34 ;;; a class stores two critical pieces of information:
35 ;;;
36 ;;;  - a count of the number of "reasons" why the class can't go
37 ;;;    into the class precedence list yet.
38 ;;;
39 ;;;  - a list of the "reasons" this class prevents others from
40 ;;;    going in until after it
41 ;;
42 ;;; A "reason" is essentially a single local precedence constraint. If a
43 ;;; constraint between two classes arises more than once it generates more
44 ;;; than one reason. This makes things simpler, linear, and isn't a problem
45 ;;; as long as we make sure to keep track of each instance of a "reason".
46 ;;;
47 ;;; This code is divided into three phases.
48 ;;;
49 ;;;  - the first phase simply generates the CPD's for each of the class
50 ;;;    and its superclasses. The remainder of the code will manipulate
51 ;;;    these CPDs rather than the class objects themselves. At the end
52 ;;;    of this pass, the CPD-SUPERS field of a CPD is a list of the CPDs
53 ;;;    of the direct superclasses of the class.
54 ;;;
55 ;;;  - the second phase folds all the local constraints into the CPD
56 ;;;    structure. The CPD-COUNT of each CPD is built up, and the
57 ;;;    CPD-AFTER fields are augmented to include precedence constraints
58 ;;;    from the CPD-SUPERS field and from the order of classes in other
59 ;;;    CPD-SUPERS fields.
60 ;;;
61 ;;;    After this phase, the CPD-AFTER field of a class includes all the
62 ;;;    direct superclasses of the class plus any class that immediately
63 ;;;    follows the class in the direct superclasses of another. There
64 ;;;    can be duplicates in this list. The CPD-COUNT field is equal to
65 ;;;    the number of times this class appears in the CPD-AFTER field of
66 ;;;    all the other CPDs.
67 ;;;
68 ;;;  - In the third phase, classes are put into the precedence list one
69 ;;;    at a time, with only those classes with a CPD-COUNT of 0 being
70 ;;;    candidates for insertion. When a class is inserted , every CPD
71 ;;;    in its CPD-AFTER field has its count decremented.
72 ;;;
73 ;;;    In the usual case, there is only one candidate for insertion at
74 ;;;    any point. If there is more than one, the specified tiebreaker
75 ;;;    rule is used to choose among them.
76
77 (defmethod compute-class-precedence-list ((root class))
78   (compute-std-cpl root (class-direct-superclasses root)))
79
80 (defstruct (class-precedence-description
81             (:conc-name nil)
82             (:print-object (lambda (obj str)
83                              (print-unreadable-object (obj str :type t)
84                                (format str "~D" (cpd-count obj)))))
85             (:constructor make-cpd ())
86             (:copier nil))
87   (cpd-class  nil)
88   (cpd-supers ())
89   (cpd-after  ())
90   (cpd-count  0))
91
92 (defun compute-std-cpl (class supers)
93   (cond
94     ;; the first two branches of this COND are implementing an
95     ;; optimization for single inheritance.
96     ((and (null supers)
97           (not (forward-referenced-class-p class)))
98      (list class))
99     ((and (null (cdr supers))
100           (not (forward-referenced-class-p (car supers))))
101      (cons class
102            (compute-std-cpl (car supers)
103                             (class-direct-superclasses (car supers)))))
104     (t
105      (multiple-value-bind (all-cpds nclasses)
106          (compute-std-cpl-phase-1 class supers)
107        (compute-std-cpl-phase-2 all-cpds)
108        (compute-std-cpl-phase-3 class all-cpds nclasses)))))
109
110 (defvar *compute-std-cpl-class->entry-table-size* 60)
111
112 (defun compute-std-cpl-phase-1 (class supers)
113   (let ((nclasses 0)
114         (all-cpds ())
115         (table (make-hash-table :size *compute-std-cpl-class->entry-table-size*
116                                 :test #'eq)))
117     (declare (fixnum nclasses))
118     (labels ((get-cpd (c)
119                (or (gethash c table)
120                    (setf (gethash c table) (make-cpd))))
121              (walk (c supers)
122                (if (forward-referenced-class-p c)
123                    (cpl-forward-referenced-class-error class c)
124                    (let ((cpd (get-cpd c)))
125                      (unless (cpd-class cpd)    ;If we have already done this
126                                                 ;class before, we can quit.
127                        (setf (cpd-class cpd) c)
128                        (incf nclasses)
129                        (push cpd all-cpds)
130                        (setf (cpd-supers cpd) (mapcar #'get-cpd supers))
131                        (dolist (super supers)
132                          (walk super (class-direct-superclasses super))))))))
133       (walk class supers)
134       (values all-cpds nclasses))))
135
136 (defun compute-std-cpl-phase-2 (all-cpds)
137   (dolist (cpd all-cpds)
138     (let ((supers (cpd-supers cpd)))
139       (when supers
140         (setf (cpd-after cpd) (nconc (cpd-after cpd) supers))
141         (incf (cpd-count (car supers)) 1)
142         (do* ((t1 supers t2)
143               (t2 (cdr t1) (cdr t1)))
144              ((null t2))
145           (incf (cpd-count (car t2)) 2)
146           (push (car t2) (cpd-after (car t1))))))))
147
148 (defun compute-std-cpl-phase-3 (class all-cpds nclasses)
149   (let ((candidates ())
150         (next-cpd nil)
151         (rcpl ()))
152
153     ;; We have to bootstrap the collection of those CPD's that
154     ;; have a zero count. Once we get going, we will maintain
155     ;; this list incrementally.
156     (dolist (cpd all-cpds)
157       (when (zerop (cpd-count cpd)) (push cpd candidates)))
158
159     (loop
160       (when (null candidates)
161
162         ;; If there are no candidates, and enough classes have been put
163         ;; into the precedence list, then we are all done. Otherwise
164         ;; it means there is a consistency problem.
165         (if (zerop nclasses)
166             (return (reverse rcpl))
167             (cpl-inconsistent-error class all-cpds)))
168
169       ;; Try to find the next class to put in from among the candidates.
170       ;; If there is only one, its easy, otherwise we have to use the
171       ;; famous RPG tiebreaker rule. There is some hair here to avoid
172       ;; having to call DELETE on the list of candidates. I dunno if
173       ;; its worth it but what the hell.
174       (setq next-cpd
175             (if (null (cdr candidates))
176                 (prog1 (car candidates)
177                        (setq candidates ()))
178                 (block tie-breaker
179                   (dolist (c rcpl)
180                     (let ((supers (class-direct-superclasses c)))
181                       (if (memq (cpd-class (car candidates)) supers)
182                           (return-from tie-breaker (pop candidates))
183                           (do ((loc candidates (cdr loc)))
184                               ((null (cdr loc)))
185                             (let ((cpd (cadr loc)))
186                               (when (memq (cpd-class cpd) supers)
187                                 (setf (cdr loc) (cddr loc))
188                                 (return-from tie-breaker cpd))))))))))
189       (decf nclasses)
190       (push (cpd-class next-cpd) rcpl)
191       (dolist (after (cpd-after next-cpd))
192         (when (zerop (decf (cpd-count after)))
193           (push after candidates))))))
194 \f
195 ;;;; support code for signalling nice error messages
196
197 (defun cpl-error (class format-string &rest format-args)
198   (error "While computing the class precedence list of the class ~A.~%~A"
199           (if (class-name class)
200               (format nil "named ~S" (class-name class))
201               class)
202           (apply #'format nil format-string format-args)))
203
204 (defun cpl-forward-referenced-class-error (class forward-class)
205   (flet ((class-or-name (class)
206            (if (class-name class)
207                (format nil "named ~S" (class-name class))
208                class)))
209     (if (eq class forward-class)
210         (cpl-error class
211                    "The class ~A is a forward referenced class."
212                    (class-or-name class))
213         (let ((names (mapcar #'class-or-name
214                              (cdr (find-superclass-chain class forward-class)))))
215           (cpl-error class
216                      "The class ~A is a forward referenced class.~@
217                       The class ~A is ~A."
218                      (class-or-name forward-class)
219                      (class-or-name forward-class)
220                      (if (null (cdr names))
221                          (format nil
222                                  "a direct superclass of the class ~A"
223                                  (class-or-name class))
224                          (format nil
225                                  "reached from the class ~A by following~@
226                               the direct superclass chain through: ~A~
227                               ~%  ending at the class ~A"
228                                  (class-or-name class)
229                                  (format nil
230                                          "~{~%  the class ~A,~}"
231                                          (butlast names))
232                                  (car (last names)))))))))
233
234 (defun find-superclass-chain (bottom top)
235   (labels ((walk (c chain)
236              (if (eq c top)
237                  (return-from find-superclass-chain (nreverse chain))
238                  (dolist (super (class-direct-superclasses c))
239                    (walk super (cons super chain))))))
240     (walk bottom (list bottom))))
241
242 (defun cpl-inconsistent-error (class all-cpds)
243   (let ((reasons (find-cycle-reasons all-cpds)))
244     (cpl-error class
245       "It is not possible to compute the class precedence list because~@
246        there ~A in the local precedence relations.~@
247        ~A because:~{~%  ~A~}."
248       (if (cdr reasons) "are circularities" "is a circularity")
249       (if (cdr reasons) "These arise" "This arises")
250       (format-cycle-reasons (apply #'append reasons)))))
251
252 (defun format-cycle-reasons (reasons)
253   (flet ((class-or-name (cpd)
254            (let ((class (cpd-class cpd)))
255              (if (class-name class)
256                  (format nil "named ~S" (class-name class))
257                  class))))
258     (mapcar
259       (lambda (reason)
260         (ecase (caddr reason)
261           (:super
262            (format
263             nil
264             "The class ~A appears in the supers of the class ~A."
265             (class-or-name (cadr reason))
266             (class-or-name (car reason))))
267           (:in-supers
268            (format
269             nil
270             "The class ~A follows the class ~A in the supers of the class ~A."
271             (class-or-name (cadr reason))
272             (class-or-name (car reason))
273             (class-or-name (cadddr reason))))))
274       reasons)))
275
276 (defun find-cycle-reasons (all-cpds)
277   (let ((been-here ())     ; list of classes we have visited
278         (cycle-reasons ()))
279
280     (labels ((chase (path)
281                (if (memq (car path) (cdr path))
282                    (record-cycle (memq (car path) (nreverse path)))
283                    (unless (memq (car path) been-here)
284                      (push (car path) been-here)
285                      (dolist (after (cpd-after (car path)))
286                        (chase (cons after path))))))
287              (record-cycle (cycle)
288                (let ((reasons ()))
289                  (do* ((t1 cycle t2)
290                        (t2 (cdr t1) (cdr t1)))
291                       ((null t2))
292                    (let ((c1 (car t1))
293                          (c2 (car t2)))
294                      (if (memq c2 (cpd-supers c1))
295                          (push (list c1 c2 :super) reasons)
296                          (dolist (cpd all-cpds)
297                            (when (memq c2 (memq c1 (cpd-supers cpd)))
298                              (return
299                                (push (list c1 c2 :in-supers cpd) reasons)))))))
300                  (push (nreverse reasons) cycle-reasons))))
301
302       (dolist (cpd all-cpds)
303         (unless (zerop (cpd-count cpd))
304           (chase (list cpd))))
305
306       cycle-reasons)))
307