forked from ruricolist/serapeum
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmop.lisp
109 lines (93 loc) · 4.5 KB
/
mop.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
(defpackage #:serapeum/mop
(:use #:c2cl #:alexandria)
(:import-from #:serapeum
#:standard/context #:topmost-object-class
#:supertypep)
#+sb-package-locks (:implement :serapeum :serapeum/mop))
(in-package :serapeum/mop)
;;; See https://github.com/tfeb/interim-lisp/blob/master/wrapping-standard.lisp
;;; and http://www.lispworks.com/documentation/HyperSpec/Body/m_defi_4.htm
(define-method-combination standard/context ()
((around (:around))
(context (:context) :order :most-specific-last)
(before (:before))
;; In the example standard method combination in the CL spec, they
;; have `(primary () :required t)`, but this is bad UI: with
;; `:required t' we get an opaque `method-combination-error' rather
;; than a useful `no-applicable-method` error that tells us what
;; the arguments were.
(primary ())
(after (:after)))
(flet ((call-methods (methods)
(mapcar (lambda (method)
`(call-method ,method))
methods)))
(let* ((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))))
(around-form (if around
`(call-method ,(first around)
(,@(rest around)
(make-method ,form)))
form)))
(if context
`(call-method ,(first context)
(,@(rest context)
(make-method ,around-form)))
around-form))))
(setf (documentation 'standard/context 'method-combination)
"A method combination which extends the standard method combination with support `:context` qualifiers. Context methods are executed before around methods, and in least-specific–first order.
Context methods are intended to set up a dynamic context that all specializations for subclasses can take for granted.
In many cases context methods can simplify implementation by avoiding the need to have a separate, internal wrapper function for a generic function exposed for extension.")
;;; Cf. http://www.cliki.net/MOP%20design%20patterns
(defclass topmost-object-class (standard-class)
((topmost-class
:initarg :topmost-class
:type symbol
:reader topmost-class)))
(defmethod print-object ((self topmost-object-class) stream)
(print-unreadable-object (self stream :type t :identity t)
(with-slots (topmost-class) self
(format stream "~a" topmost-class)))
self)
(defmethod validate-superclass ((class1 topmost-object-class)
(class2 standard-class))
t)
(defun insert-superclass (superclass list)
;; NB The version on the Cliki has the new superclass appended to
;; the direct superclasses, but that won't work if `standard-object'
;; is already one of the superclasses and is itself a superclass of
;; the superclass.
(cond ((null list) (list superclass))
((subtypep superclass (first list))
(cons superclass list))
(t
(cons (first list)
(insert-superclass superclass (rest list))))))
(defmethod initialize-instance :around
((class topmost-object-class) &rest initargs &key direct-superclasses
topmost-class)
(let ((superclass topmost-class))
(if (find superclass direct-superclasses :test #'supertypep)
(call-next-method)
(apply #'call-next-method
class
:direct-superclasses (insert-superclass (find-class superclass)
direct-superclasses)
initargs))))
(defmethod reinitialize-instance :around
((class topmost-object-class) &rest initargs &key (direct-superclasses nil direct-superclasses-supplied?))
(let ((superclass (topmost-class class)))
(cond ((not direct-superclasses-supplied?)
(call-next-method))
((find superclass direct-superclasses :test #'supertypep)
(call-next-method))
(t (apply #'call-next-method
class
:direct-superclasses (insert-superclass (find-class superclass)
direct-superclasses)
initargs)))))