root/trunk/thirdparty/cl-store_0.8.4/circularities.lisp

Revision 2554, 9.7 kB (checked in by ksprotte, 11 months ago)

added cl-store

  • Property svn:executable set to *
Line 
1 ;;; -*- Mode: LISP; Syntax: ANSI-Common-Lisp; Base: 10 -*-
2 ;; See the file LICENCE for licence information.
3
4 ;; Defines a special backend type which specializes various methods
5 ;; in plumbing.lisp to make it nice and easy to
6 ;; resolve possible circularities in objects.
7 ;; Most of the work is done using the resolving-object
8 ;; macro which knows how to handle an object which
9 ;; is a referrer to a previously restored value.
10 ;; Backends wanting to make use of this should take
11 ;; a look at default-backend.lisp and xml-backend.lisp
12 ;; paying special attention to the defbackend form and the
13 ;; defrestore definitions for cons, array, simple-vector
14 ;; array and hash-table.
15 ;;
16 ;; As a note this will ignore integers, symbols or characters
17 ;; as referrer values. It will handle all other EQ number although
18 ;; software depending on eq numbers are not conforming
19 ;; programs according to the Hyperspec(notes in EQ).
20
21 (in-package :cl-store)
22
23 (defvar *check-for-circs* t)
24
25 (defstruct delay
26   value (completed nil))
27
28 (defmacro delay (&rest body)
29   `(make-delay :value #'(lambda () ,@body)))
30
31 (defun force (delay)
32   (unless (delay-completed delay)
33     (setf (delay-value delay) (funcall (the function (delay-value delay)))
34           (delay-completed delay) t))
35   (delay-value delay))
36
37
38 ;; The definitions for setting and setting-hash sits in resolving-object.
39 (defmacro setting (place get)
40   "Resolve the possible referring object retrieved by GET and
41   set it into PLACE. Only usable within a resolving-object form."
42   (declare (ignore place get))
43   #+ecl nil
44   #-ecl (error "setting can only be used inside a resolving-object form."))
45
46 (defmacro setting-hash (getting-key getting-value)
47   "Insert the value retrieved by GETTING-VALUE with the key
48   retrieved by GETTING-KEY, resolving possible circularities.
49   Only usable within a resolving-object form."
50   (declare (ignore getting-key getting-value))
51   #+ecl nil
52   #-ecl (error "setting-hash can only be used inside a resolving-object form."))
53
54 (defmacro resolving-object ((var create) &body body)
55   "Execute body attempting to resolve circularities found in
56    form CREATE."
57   (with-gensyms (value key)
58     `(macrolet ((setting (place getting)
59                   `(let ((,',value ,getting))
60                      (if (referrer-p ,',value)
61                          (if *check-for-circs*
62                              (push (delay (setf ,place
63                                                 (referred-value ,',value
64                                                                 *restored-values*)))
65                                    *need-to-fix*)
66                              (restore-error "Found a circular values with *check-for-circs* = nil"))
67                          (setf ,place ,',value))))
68                 (setting-hash (getting-key getting-place)
69                   `(let ((,',key ,getting-key))
70                      (if (referrer-p ,',key)
71                          (let ((,',value ,getting-place))
72                            (unless *check-for-circs*
73                              (restore-error "Found a circular values with *check-for-circs* = nil"))
74                            (push (delay (setf (gethash (referred-value ,',key *restored-values*)
75                                                        ,',var)
76                                               (if (referrer-p ,',value)
77                                                   (referred-value ,',value *restored-values*)
78                                                   ,',value)))
79                                  *need-to-fix*))
80                          (setting (gethash ,',key ,',var) ,getting-place)))))
81        (let ((,var ,create))
82          ,@body
83          ,var))))
84
85 (defstruct referrer val)
86
87 (defun referred-value (referrer hash)
88   "Return the value REFERRER is meant to be by looking in HASH."
89   (gethash (referrer-val referrer)
90            hash))
91
92 (defclass resolving-backend (backend)
93   ()
94   (:documentation "A backend which does the setup for resolving circularities."))
95
96 (declaim (type (or fixnum null) *stored-counter*))
97 (defvar *stored-counter*)
98 (defvar *stored-values*)
99
100 (defvar *store-hash-size* 50)
101
102 (defvar *grouped-store-hash*)
103 (defvar *grouped-restore-hash*)
104
105 (defun create-serialize-hash ()
106   (make-hash-table :test #'eql :size *store-hash-size*))
107
108 (defmacro with-serialization-unit ((&key store-hash restore-hash)
109                                    &body body)
110   "Executes body in a single serialization unit allowing various internal data
111 structures to be reused.
112 The keys store-hash and restore-hash are expected to be either nil or
113 hash-tables as produced by the function create-serialize-hash."
114   `(let ((*grouped-store-hash* (or ,store-hash (create-serialize-hash)))
115          (*grouped-restore-hash* (or ,restore-hash (create-serialize-hash))))
116      ,@body))
117
118 (defun get-store-hash ()
119   (when *check-for-circs*
120     (if (boundp '*grouped-store-hash*)
121         (clrhash *grouped-store-hash*)
122         (create-serialize-hash))))
123
124 (defun get-restore-hash ()
125   (when *check-for-circs*
126     (if (boundp '*grouped-restore-hash*)
127         (clrhash *grouped-restore-hash*)
128         (create-serialize-hash))))
129
130 (defmethod backend-store :around ((backend resolving-backend) (place t) (obj t))
131   (call-next-method))
132
133 (defmethod backend-store ((backend resolving-backend) (place stream) (obj t))
134   "Store OBJ into PLACE. Does the setup for counters and seen values."
135   (declare (optimize speed (safety 1) (debug 0)))
136   (let ((*stored-counter* 0)
137         (*stored-values* (get-store-hash)))
138     (store-backend-code backend place)
139     (backend-store-object backend obj place)
140     obj))
141
142 (defun seen (obj)
143   "Has this object already been stored?"
144   (declare (optimize speed (safety 0) (debug 0)))
145   (incf *stored-counter*)
146   (gethash obj *stored-values*))
147
148 (defun update-seen (obj)
149   "Register OBJ as having been stored."
150   (declare (optimize speed (safety 0) (debug 0)))
151   (setf (gethash obj *stored-values*) *stored-counter*)
152   nil)
153
154 (deftype not-circ ()
155   "Type grouping integers and characters, which we
156   don't bother to check if they have been stored before"
157   '(or integer character))
158
159 (defun needs-checkp (obj)
160   "Do we need to check if this object has been stored before?"
161   (not (typep obj 'not-circ)))
162
163 (defgeneric store-referrer (backend obj place)
164   (:documentation "Store the number OBJ into PLACE as a referrer for BACKEND.")
165   (:method ((backend resolving-backend) (obj t) (place t))
166     (store-error  "store-referrer must be specialized for backend ~(~A~)."
167                   (name backend))))
168
169
170 (defun get-ref (obj)
171   (declare (optimize speed (safety 0) (debug 0)))
172   (if (needs-checkp obj)
173       (multiple-value-bind (val win) (seen obj)
174         (if (or val win)
175             val
176             (update-seen obj)))
177       nil))
178
179 (defmethod backend-store-object ((backend resolving-backend) (obj t) (place t))
180   "Store object if we have not seen this object before, otherwise retrieve
181   the referrer object for it and store that using store-referrer."
182   (aif (and *check-for-circs* (get-ref obj))
183        (store-referrer backend it place)
184        (internal-store-object backend obj place)))
185        
186 ;; Restoration.
187 (declaim (type (or fixnum null) *restore-counter*))
188 (defvar *restore-counter*)
189 (defvar *need-to-fix*)
190 (defvar *restored-values*)
191 (defvar *restore-hash-size* 50)
192
193 (defmethod backend-restore ((backend resolving-backend) (place stream))
194   "Restore an object from PLACE using BACKEND. Does the setup for
195   various variables used by resolving-object."
196   (let ((*restore-counter* 0)
197         (*need-to-fix* nil)
198         (*restored-values* (get-restore-hash)))
199     (check-magic-number backend place)
200     (prog1
201       (backend-restore-object backend place)
202       (dolist (fn *need-to-fix*)
203         (force fn)))))
204
205 (defun update-restored (spot val)
206   (declare (optimize speed (safety 0) (debug 0)))
207   (setf (gethash spot *restored-values*) val))
208
209 (defun handle-normal (backend reader place)
210   (declare (optimize speed (safety 1) (debug 0)))
211   (let ((spot (incf *restore-counter*))
212         (vals (new-val (internal-restore-object backend reader place))))
213     (update-restored spot vals)
214     vals))
215
216 (defgeneric referrerp (backend reader)
217   (:method ((backend t) (reader t))
218    (error "referrerp must be specialized for backend ~A." (name backend))))
219
220 (defun handle-restore (place backend)
221   (declare (optimize speed (safety 1) (debug 0)))
222   (let ((reader (get-next-reader backend place)))
223     (declare (type symbol reader))
224     (cond ((referrerp backend reader)
225            (incf *restore-counter*)
226            (new-val (internal-restore-object backend reader place)))
227           ((not (int-or-char-p backend reader))
228            (handle-normal backend reader place))
229           (t (new-val (internal-restore-object backend reader place))))))
230
231 (defmethod backend-restore-object ((backend resolving-backend) (place t))
232   "Retrieve a object from PLACE, does housekeeping for circularity fixing."
233   (declare (optimize speed (safety 1) (debug 0)))
234   (if *check-for-circs*
235       (handle-restore place backend)
236       (call-next-method)))
237
238 ; This used to be called int-sym-or-char-p
239 ; but was renamed to handle eq symbols (gensym's mainly).
240 ; The basic concept is that we don't bother
241 ; checking for circularities with integers or
242 ; characters since these aren't gauranteed to be eq
243 ; even if they are the same object.
244 ; (notes for eq in CLHS).
245 (defgeneric int-or-char-p (backend fn)
246   (:method ((backend backend) (fn symbol))
247     "Is function FN registered to restore an integer or character in BACKEND."
248     (member fn '(integer character))))
249
250 (defun new-val (val)
251   "Tries to get a referred value to reduce unnecessary cirularity fixing."
252   (declare (optimize speed (safety 1) (debug 0)))
253   (if (referrer-p val)
254       (multiple-value-bind (new-val win) (referred-value val *restored-values*)
255         (if (or new-val win)
256             new-val
257             val))
258       val))
259
260 ;; EOF
Note: See TracBrowser for help on using the browser.