Coverage report: /home/ellis/comp/core/lib/obj/meta/filtered.lisp
Kind | Covered | All | % |
expression | 0 | 370 | 0.0 |
branch | 0 | 46 | 0.0 |
Key
Not instrumented
Conditionalized out
Executed
Not executed
Both branches taken
One branch taken
Neither branch taken
1
;;; obj/meta/filtered.lisp --- Filtered generic functions
3
;; see https://github.com/pcostanza/filtered-functions
5
;; ref: https://www.p-cos.net/documents/filtered-dispatch.pdf
6
#| ;; factorial as a generic function
7
(defmethod fac ((n number))
10
(defmethod fac ((n (eql 0)))
14
(in-package :obj/meta/filtered)
16
(defgeneric generic-function-filter-expression (gf)
17
(:method ((gf generic-function)) (constantly t)))
19
(defclass simple-filtered-function (standard-generic-function)
20
((filter-expression :initarg :filter-expression
21
:reader generic-function-filter-expression))
22
(:metaclass sb-mop:funcallable-standard-class))
24
(defmethod compute-applicable-methods-using-classes ((ff simple-filtered-function) classes)
25
(declare (ignore classes))
28
(defmethod compute-applicable-methods ((ff simple-filtered-function) required-args)
29
(let* ((filter-expression (generic-function-filter-expression ff))
30
(filter-functions (apply filter-expression required-args)))
31
(cond ((consp filter-functions)
32
(loop for arg in required-args
33
for filter-function = (pop filter-functions)
34
collect (if filter-function
35
(funcall filter-function arg)
38
finally (return (call-next-method ff filtered-args))))
39
((null filter-functions) '())
40
((eq filter-functions 't) (call-next-method))
41
(t (call-next-method ff (cons (funcall filter-functions (first required-args))
42
(rest required-args)))))))
44
(defclass filtered-function (standard-generic-function)
45
((filter-groups :initform '() :reader %filter-groups))
46
(:metaclass sb-mop:funcallable-standard-class))
48
(defun check-filters (ff filters)
49
(when (assoc nil filters)
50
(error "NIL is not a valid filter key in filtered function ~S." ff))
51
(loop for (first . rest) on (mapcar #'first filters)
52
when (member first rest)
53
do (error "Duplicate filter specification ~S in filtered function ~S." first ff))
54
(loop for (key . filter) in filters
55
unless (or (symbolp filter) (functionp filter))
56
do (error "~S is not a valid function designator for filter ~S in filtered function ~S."
59
(defmethod initialize-instance :after ((ff filtered-function) &key filters)
60
(check-filters ff filters)
61
(loop with initargs = `(,@(handler-case
63
(sb-mop:generic-function-lambda-list ff)
64
:argument-precedence-order
65
(sb-mop:generic-function-argument-precedence-order ff))
67
:method-class ,(sb-mop:generic-function-method-class ff)
68
:method-combination ,(sb-mop:generic-function-method-combination ff))
69
for (key . filter) in filters
70
collect (cons key (apply #'make-instance
71
'simple-filtered-function
72
:filter-expression filter
73
initargs)) into filter-groups
75
(setf (slot-value ff 'filter-groups) filter-groups)))
77
(defmethod reinitialize-instance :after
78
((ff filtered-function) &rest initargs &key (filters '() filtersp))
80
(loop initially (check-filters ff filters)
81
with initargs = `(,@(handler-case
83
(sb-mop:generic-function-lambda-list ff)
84
:argument-precedence-order
85
(sb-mop:generic-function-argument-precedence-order ff))
87
:method-class ,(sb-mop:generic-function-method-class ff)
88
:method-combination ,(sb-mop:generic-function-method-combination ff))
89
with old-filter-groups = (%filter-groups ff)
90
for (key . filter) in filters
91
for old-filter-group = (cdr (assoc key old-filter-groups))
92
collect (cons key (if old-filter-group
93
(apply #'reinitialize-instance
95
:filter-expression filter
97
(apply #'make-instance
98
'simple-filtered-function
99
:filter-expression filter
100
initargs))) into filter-groups
102
(setf (slot-value ff 'filter-groups) filter-groups))
103
(loop for (nil . filter-group) in (%filter-groups ff)
104
do (apply #'reinitialize-instance filter-group initargs))))
106
(defgeneric generic-function-filters (ff)
107
(:method ((ff filtered-function))
108
(loop for (key . filter-group) in (%filter-groups ff)
109
collect (cons key (generic-function-filter-expression filter-group)))))
111
(defgeneric (setf generic-function-filters) (new-filters ff)
112
(:argument-precedence-order ff new-filters)
113
(:method ((new-filters list) (ff filtered-function))
114
(reinitialize-instance ff :filters new-filters)
117
(defvar *generic-functions*)
119
(defmethod compute-applicable-methods-using-classes ((ff filtered-function) classes)
120
(declare (ignore classes))
121
(if *generic-functions*
125
(defmethod compute-applicable-methods ((ff filtered-function) args)
126
(if *generic-functions*
128
(loop for ff in *generic-functions*
129
append (compute-applicable-methods ff args))
133
(defmethod compute-discriminating-function ((ff filtered-function))
134
(flet ((compute-discriminator ()
136
for (nil . gf) in (%filter-groups ff)
137
when (sb-mop:generic-function-methods gf) do (push gf gfs)
140
(if (sb-mop:generic-function-methods ff)
141
(let ((original-discriminator (call-next-method)))
143
(let ((*generic-functions* gfs))
144
(apply original-discriminator args))))
147
(apply #'no-applicable-method ff args)))
149
(compute-discriminating-function (car gfs)))
150
(t (let ((original-discriminator (call-next-method)))
152
(let ((*generic-functions* gfs))
153
(apply original-discriminator args)))))))))))
154
(if (eq (class-of ff) (find-class 'filtered-function))
156
(let ((discriminator (compute-discriminator)))
157
(sb-mop:set-funcallable-instance-function ff discriminator)
158
(apply discriminator args)))
159
(compute-discriminator))))
161
(defgeneric method-filter (method)
162
(:method ((method method)) nil))
164
(defclass filtered-method (standard-method)
165
((filter :initform nil :reader method-filter)))
167
(defmethod initialize-instance :after ((method filtered-method) &key qualifiers)
168
(when (member (first qualifiers) '(:before :after :around))
170
(when (eq (first qualifiers) :filter)
173
(error "Filter qualifier is not followed by a filter designator in method ~S." method))
174
(unless (first qualifiers)
175
(error "NIL is not a valid filter designator in method ~S." method))
176
(setf (slot-value method 'filter)
179
(error "Invalid qualifiers ~S for method ~S." qualifiers method)))
181
(defmethod add-method ((ff filtered-function) method)
182
(let ((filter (method-filter method)))
184
(let ((spec (assoc filter (%filter-groups ff))))
185
(cond (spec (add-method (cdr spec) method))
186
(t (cerror "Try again."
187
"Invalid filter ~S in method ~S for filtered function ~S." filter method ff)
188
(add-method ff method)))
189
(reinitialize-instance ff))
190
(call-next-method))))
192
(defmethod remove-method ((ff filtered-function) method)
193
(let ((filter (method-filter method)))
195
(let ((spec (assoc filter (%filter-groups ff))))
197
(remove-method (cdr spec) method)
198
(reinitialize-instance ff)))
199
(call-next-method))))
201
(define-method-combination filtered ()
202
((methods * :required t))
203
(:generic-function gf)
205
for method in methods
206
for (qualifier) = (method-qualifiers method)
207
if (eq qualifier :around) collect method into around
208
else if (eq qualifier :before) collect method into before
209
else if (eq qualifier :after) do (push method after)
210
else collect method into primary
211
finally (unless primary
212
(method-combination-error
213
"No applicable primary method for generic function ~S." gf))
215
(flet ((call-methods (methods)
216
(loop for method in methods collect `(call-method ,method))))
217
(let* ((inner-form (if (or before after (rest primary))
218
`(multiple-value-prog1
219
(progn ,@(call-methods before)
220
(call-method ,(first primary) ,(rest primary)))
221
,@(call-methods (reverse after)))
222
`(call-method ,(first primary))))
223
(outer-form (if around
224
`(call-method ,(first around)
226
(make-method ,inner-form)))
230
(defmacro define-filtered-function (name (&rest lambda-list) &body options)
232
(defgeneric ,name ,lambda-list
233
,@(unless (member :generic-function-class options :key #'first)
234
'((:generic-function-class filtered-function)))
235
,@(unless (member :method-class options :key #'first)
236
'((:method-class filtered-method)))
237
,@(unless (member :method-combination options :key #'first)
238
'((:method-combination filtered)))
239
,@(remove :filters options :key #'first))
240
(setf (generic-function-filters (fdefinition ',name))
241
,(let ((filters-option (rest (assoc :filters options)))
242
(required-lambda-list (loop for arg in lambda-list
243
until (member arg lambda-list-keywords)
245
`(list ,@(loop for (key . body) in filters-option
246
collect `(cons ',key (lambda ,required-lambda-list
247
(declare (ignorable ,@required-lambda-list))