summaryrefslogtreecommitdiff
path: root/generic.lisp
blob: d16052749e0c139f5e6b62e4a61e1a9b4bbc8643 (plain)
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
;;;; An implementation of a kind of generic function. This is not
;;;; terribly CLOSy, as it does not support inheritance. What we have
;;;; is a method selection routine that works on arbitrary types.
;;;; Some effort has been made to emulate CLOS's primary method
;;;; selection.

(in-package :def)

(defvar *genfuns* (make-hash-table :test #'eq)
  "Mapping from symbol names to GENFUN instances.")

(defclass genfun ()
  ((name
    :reader genfun-name
    :initarg :name 
    :type symbol)
   (methods
    :accessor genfun-methods
    :type (or null cons)
    :initform nil
    :documentation
    "A sorted association list, keyed by TYPE SPECIFIER, storing
    S-EXPRESSIONS representing the method implementation for that
    type. This list is sorted in topological order according the
    natural order on the type keys.")
   (lambda-list
    :reader genfun-lambda-list
    :initarg :lambda-list
    :type (or null cons)
    :documentation
    "The lambda list for this generic function")
   (docstring
    :reader genfun-docstring
    :initarg :docstring
    :type (or null string))))

(defun type-specifier-p (thing)
  (ignore-errors
   (typep nil thing)
   t))

(deftype lambda-list-option-term ()
  '(member cl:&rest cl:&aux cl:&optional cl:&key))

(defun expand-generic-function-def (generic)
  (let ((vars
          (method-sig-vars (genfun-lambda-list generic)))
        (types
          (gensym "TYPES-")))
    `(defun ,(genfun-name generic) ,(genfun-lambda-list generic)
       ,(genfun-docstring generic)
       (let ((,types (mapcar #'type-of (list ,@vars)))) 
         (cond
           ,@(loop :for (signature . body) :in (genfun-methods generic)
                   :collect `((every #'subtypep ,types ',signature) ,@body) :into clauses
                   :finally
                      (return (nconc clauses
                                 (list
                                  `(t (error "No implementation of ~s for ~s"
                                            ',(genfun-name generic)
                                            ,types)))))))))))
(defun method-signature (lambda-list)
  (loop
    :for param :in lambda-list
    :until (typep param 'lambda-list-option-term)
    :when (and (listp param) (= 2 (length param)))
      :do (assert (type-specifier-p (second param)) ()
                  "~s is not a valid type-specifier"
                  (second param))
      :and :collect (second param)
    :else
      :collect 't))

(defun method-sig-vars (gen-lambda-list)
  (loop :for x :in gen-lambda-list
        :until (typep x 'lambda-list-option-term)
        :collect x))

(defun sig-before-p (sig1 sig2)
  (let* ((before (mapcar #'subtypep sig1 sig2))
         (after (mapcar #'subtypep sig2 sig1)))
    (or (every #'identity before)
        (and (find 't before) (find 't after)
             (< (position 't before) (position 't after))))))

(defun add-meth (gen spec)
  (with-slots (methods) gen
    (push spec methods)
    (setf methods (sort methods #'sig-before-p :key #'car))))


(defun method-lambda-list-matches-generic (mll gll)
  (and (= (length mll) (length gll))
       (loop :for m :in mll
             :for g :in gll
             :for mvar := (if (consp m) (first m) m)
             :for gvar := (if (consp g) (first g) g)
             :always (string-equal mvar gvar))))

(defmacro generic (name lambda-list &optional docstring)
  "Define a generic function with optional docstring"
  (expand-generic-function-def
   (setf (gethash name *genfuns*)
         (make-instance 'genfun
           :name name
           :lambda-list lambda-list
           :docstring docstring))))

(defmacro method (name lambda-list &body body)
  "Define a method on the generic function called NAME, which must exist already."
  (let ((gen (gethash name *genfuns*)))
    (unless gen
      (error "Unknown GENFUN named ~s" name))
    (unless (method-lambda-list-matches-generic lambda-list (genfun-lambda-list gen))
      (error "Method lambda-list ~s does not match generic function ~s"
             lambda-list (genfun-lambda-list gen)))
    (let* ((sig
             (method-signature lambda-list))
           (bound
             (assoc sig (genfun-methods gen) :test #'equalp)))
      (if bound
          (rplacd bound body)
          (add-meth gen (cons sig body)))
      (expand-generic-function-def gen))))