-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathfiltered-functions.lisp
266 lines (244 loc) · 12.2 KB
/
filtered-functions.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
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
(in-package :filtered-functions)
(defgeneric generic-function-filter-expression (gf)
(:method ((gf generic-function)) (constantly t)))
(defclass simple-filtered-function (standard-generic-function)
((filter-expression :initarg :filter-expression
:reader generic-function-filter-expression))
(:metaclass funcallable-standard-class))
(defmethod compute-applicable-methods-using-classes ((ff simple-filtered-function) classes)
(declare (ignore classes))
(values '() nil))
(defmethod compute-applicable-methods ((ff simple-filtered-function) required-args)
(let* ((filter-expression (generic-function-filter-expression ff))
(filter-functions (apply filter-expression required-args)))
(cond ((consp filter-functions)
(loop for arg in required-args
for filter-function = (pop filter-functions)
collect (if filter-function
(funcall filter-function arg)
arg)
into filtered-args
finally (return (call-next-method ff filtered-args))))
((null filter-functions) '())
((eq filter-functions 't) (call-next-method))
(t (call-next-method ff (cons (funcall filter-functions (first required-args))
(rest required-args)))))))
(defclass filtered-function (standard-generic-function)
((filter-groups :initform '() :reader %filter-groups))
(:metaclass funcallable-standard-class))
(defun check-filters (ff filters)
(when (assoc nil filters)
(error "NIL is not a valid filter key in filtered function ~S." ff))
(loop for (first . rest) on (mapcar #'first filters)
when (member first rest)
do (error "Duplicate filter specification ~S in filtered function ~S." first ff))
(loop for (key . filter) in filters
unless (or (symbolp filter) (functionp filter))
do (error "~S is not a valid function designator for filter ~S in filtered function ~S."
filter key ff)))
(defmethod initialize-instance :after
((ff filtered-function) &key filters)
(check-filters ff filters)
(loop with initargs = `(,@(handler-case
(list :lambda-list
(generic-function-lambda-list ff)
:argument-precedence-order
(generic-function-argument-precedence-order ff))
(error () '()))
:method-class ,(generic-function-method-class ff)
:method-combination ,(generic-function-method-combination ff))
for (key . filter) in filters
collect (cons key (apply #'make-instance
'simple-filtered-function
:filter-expression filter
initargs)) into filter-groups
finally
(setf (slot-value ff 'filter-groups) filter-groups)))
(defmethod reinitialize-instance :after
((ff filtered-function) &rest initargs &key (filters '() filtersp))
(if filtersp
(loop initially (check-filters ff filters)
with initargs = `(,@(handler-case
(list :lambda-list
(generic-function-lambda-list ff)
:argument-precedence-order
(generic-function-argument-precedence-order ff))
(error () '()))
:method-class ,(generic-function-method-class ff)
:method-combination ,(generic-function-method-combination ff))
with old-filter-groups = (%filter-groups ff)
for (key . filter) in filters
for old-filter-group = (cdr (assoc key old-filter-groups))
collect (cons key (if old-filter-group
(apply #'reinitialize-instance
old-filter-group
:filter-expression filter
initargs)
(apply #'make-instance
'simple-filtered-function
:filter-expression filter
initargs))) into filter-groups
finally
(setf (slot-value ff 'filter-groups) filter-groups))
(loop for (nil . filter-group) in (%filter-groups ff)
do (apply #'reinitialize-instance filter-group initargs))))
(defgeneric generic-function-filters (ff)
(:method ((ff filtered-function))
(loop for (key . filter-group) in (%filter-groups ff)
collect (cons key (generic-function-filter-expression filter-group)))))
(defgeneric (setf generic-function-filters) (new-filters ff)
(:argument-precedence-order ff new-filters)
(:method ((new-filters list) (ff filtered-function))
(reinitialize-instance ff :filters new-filters)
new-filters))
(defvar *generic-functions*)
(defmethod compute-applicable-methods-using-classes ((ff filtered-function) classes)
(declare (ignore classes))
(if *generic-functions*
(values '() nil)
(call-next-method)))
(defmethod compute-applicable-methods ((ff filtered-function) args)
(if *generic-functions*
(append
(loop for ff in *generic-functions*
append (compute-applicable-methods ff args))
(call-next-method))
(call-next-method)))
#-ecl
(defmethod compute-discriminating-function ((ff filtered-function))
(flet ((compute-discriminator ()
(loop with gfs
for (nil . gf) in (%filter-groups ff)
when (generic-function-methods gf) do (push gf gfs)
finally
(return
(if (generic-function-methods ff)
(let ((original-discriminator (call-next-method)))
(lambda (&rest args)
(let ((*generic-functions* gfs))
(apply original-discriminator args))))
(cond ((null gfs)
(lambda (&rest args)
(apply #'no-applicable-method ff args)))
((null (cdr gfs))
(compute-discriminating-function (car gfs)))
(t (let ((original-discriminator (call-next-method)))
(lambda (&rest args)
(let ((*generic-functions* gfs))
(apply original-discriminator args)))))))))))
(if (eq (class-of ff) (find-class 'filtered-function))
(lambda (&rest args)
(let ((discriminator (compute-discriminator)))
(set-funcallable-instance-function ff discriminator)
(apply discriminator args)))
(compute-discriminator))))
#+ecl
(defmethod compute-discriminating-function ((ff filtered-function))
(let ((original-discriminator (call-next-method)))
(flet ((compute-discriminator ()
(loop with gfs
for (nil . gf) in (%filter-groups ff)
when (generic-function-methods gf) do (push gf gfs)
finally
(return
(if (generic-function-methods ff)
(lambda (&rest args)
(let ((*generic-functions* gfs))
(apply original-discriminator args)))
(cond ((null gfs)
(lambda (&rest args)
(apply #'no-applicable-method ff args)))
((null (cdr gfs))
(compute-discriminating-function (car gfs)))
(t (lambda (&rest args)
(let ((*generic-functions* gfs))
(apply original-discriminator args))))))))))
(if (eq (class-of ff) (find-class 'filtered-function))
(lambda (&rest args)
(let ((discriminator (compute-discriminator)))
(set-funcallable-instance-function ff discriminator)
(apply discriminator args)))
(compute-discriminator)))))
(defgeneric method-filter (method)
(:method ((method method)) nil))
(defclass filtered-method (standard-method)
((filter :initform nil :reader method-filter)))
(defmethod initialize-instance :after ((method filtered-method) &key qualifiers)
(when (member (first qualifiers) '(:before :after :around))
(pop qualifiers))
(when (eq (first qualifiers) :filter)
(pop qualifiers)
(unless qualifiers
(error "Filter qualifier is not followed by a filter designator in method ~S." method))
(unless (first qualifiers)
(error "NIL is not a valid filter designator in method ~S." method))
(setf (slot-value method 'filter)
(pop qualifiers)))
(when qualifiers
(error "Invalid qualifiers ~S for method ~S." qualifiers method)))
(defmethod add-method ((ff filtered-function) method)
(let ((filter (method-filter method)))
(if filter
(let ((spec (assoc filter (%filter-groups ff))))
(cond (spec (add-method (cdr spec) method))
(t (cerror "Try again."
"Invalid filter ~S in method ~S for filtered function ~S." filter method ff)
(add-method ff method)))
(reinitialize-instance ff))
(call-next-method))))
(defmethod remove-method ((ff filtered-function) method)
(let ((filter (method-filter method)))
(if filter
(let ((spec (assoc filter (%filter-groups ff))))
(when spec
(remove-method (cdr spec) method)
(reinitialize-instance ff)))
(call-next-method))))
(define-method-combination filtered ()
((methods * :required t))
(:generic-function gf)
(loop with after
for method in methods
for (qualifier) = (method-qualifiers method)
if (eq qualifier :around) collect method into around
else if (eq qualifier :before) collect method into before
else if (eq qualifier :after) do (push method after)
else collect method into primary
finally (unless primary
(method-combination-error
"No applicable primary method for generic function ~S." gf))
(return
(flet ((call-methods (methods)
(loop for method in methods collect `(call-method ,method))))
(let* ((inner-form (if (or before after (rest primary))
`(multiple-value-prog1
(progn ,@(call-methods before)
(call-method ,(first primary) ,(rest primary)))
,@(call-methods (reverse after)))
`(call-method ,(first primary))))
(outer-form (if around
`(call-method ,(first around)
(,@(rest around)
(make-method ,inner-form)))
inner-form)))
outer-form)))))
(defmacro define-filtered-function (name (&rest lambda-list) &body options)
`(progn
(defgeneric ,name ,lambda-list
,@(unless (member :generic-function-class options :key #'first)
'((:generic-function-class filtered-function)))
,@(unless (member :method-class options :key #'first)
'((:method-class filtered-method)))
,@(unless (member :method-combination options :key #'first)
'((:method-combination filtered)))
,@(remove :filters options :key #'first))
(setf (generic-function-filters (fdefinition ',name))
,(let ((filters-option (rest (assoc :filters options)))
(required-lambda-list (loop for arg in lambda-list
until (member arg lambda-list-keywords)
collect arg)))
`(list ,@(loop for (key . body) in filters-option
collect `(cons ',key (lambda ,required-lambda-list
(declare (ignorable ,@required-lambda-list))
,@body))))))
',name))