diff options
| author | Stefan Monnier | 2015-01-14 14:37:10 -0500 |
|---|---|---|
| committer | Stefan Monnier | 2015-01-14 14:37:10 -0500 |
| commit | 9def17e92bbb61e877bf092b562a92946cf43210 (patch) | |
| tree | 5af1af25989bb45fcf7029fbf9ebf66281466232 | |
| parent | e7db8e8d5de70be5e047c961cdfbf692d52e33c6 (diff) | |
| download | emacs-9def17e92bbb61e877bf092b562a92946cf43210.tar.gz emacs-9def17e92bbb61e877bf092b562a92946cf43210.zip | |
* lisp/emacs-lisp/cl-generic.el: New file.
* lisp/emacs-lisp/cl-macs.el (cl-flet): Allow (FUN EXP) forms.
(cl-load-time-value, cl-labels): Use closures rather than
backquoted lambdas.
(cl-macrolet): Use `eval' to create the function value, and support CL
style arguments in for the defined macros.
* test/automated/cl-generic-tests.el: New file.
| -rw-r--r-- | etc/NEWS | 2 | ||||
| -rw-r--r-- | lisp/ChangeLog | 10 | ||||
| -rw-r--r-- | lisp/emacs-lisp/cl-generic.el | 605 | ||||
| -rw-r--r-- | lisp/emacs-lisp/cl-macs.el | 52 | ||||
| -rw-r--r-- | test/ChangeLog | 4 | ||||
| -rw-r--r-- | test/automated/cl-generic-tests.el | 131 |
6 files changed, 787 insertions, 17 deletions
| @@ -480,6 +480,8 @@ As a result of the above, these commands are now obsolete: | |||
| 480 | 480 | ||
| 481 | * New Modes and Packages in Emacs 25.1 | 481 | * New Modes and Packages in Emacs 25.1 |
| 482 | 482 | ||
| 483 | ** cl-generic.el provides CLOS-style multiple-dispatch generic functions. | ||
| 484 | |||
| 483 | ** scss-mode (a minor variant of css-mode) | 485 | ** scss-mode (a minor variant of css-mode) |
| 484 | 486 | ||
| 485 | ** let-alist is a new macro (and a package) that allows one to easily | 487 | ** let-alist is a new macro (and a package) that allows one to easily |
diff --git a/lisp/ChangeLog b/lisp/ChangeLog index 6e2adc9b8e1..e0fb3cced0c 100644 --- a/lisp/ChangeLog +++ b/lisp/ChangeLog | |||
| @@ -1,5 +1,15 @@ | |||
| 1 | 2015-01-14 Stefan Monnier <monnier@iro.umontreal.ca> | 1 | 2015-01-14 Stefan Monnier <monnier@iro.umontreal.ca> |
| 2 | 2 | ||
| 3 | * emacs-lisp/cl-generic.el: New file. | ||
| 4 | |||
| 5 | * emacs-lisp/cl-macs.el (cl-flet): Allow (FUN EXP) forms. | ||
| 6 | (cl-load-time-value, cl-labels): Use closures rather than | ||
| 7 | backquoted lambdas. | ||
| 8 | (cl-macrolet): Use `eval' to create the function value, and support CL | ||
| 9 | style arguments in for the defined macros. | ||
| 10 | |||
| 11 | 2015-01-14 Stefan Monnier <monnier@iro.umontreal.ca> | ||
| 12 | |||
| 3 | * net/eww.el: Use lexical-binding. | 13 | * net/eww.el: Use lexical-binding. |
| 4 | (eww-links-at-point): Remove unused arg. | 14 | (eww-links-at-point): Remove unused arg. |
| 5 | (eww-mode-map): Inherit from special-mode-map. | 15 | (eww-mode-map): Inherit from special-mode-map. |
diff --git a/lisp/emacs-lisp/cl-generic.el b/lisp/emacs-lisp/cl-generic.el new file mode 100644 index 00000000000..19e4ce0fbef --- /dev/null +++ b/lisp/emacs-lisp/cl-generic.el | |||
| @@ -0,0 +1,605 @@ | |||
| 1 | ;;; cl-generic.el --- CLOS-style generic functions for Elisp -*- lexical-binding: t; -*- | ||
| 2 | |||
| 3 | ;; Copyright (C) 2015 Stefan Monnier | ||
| 4 | |||
| 5 | ;; Author: Stefan Monnier <monnier@iro.umontreal.ca> | ||
| 6 | |||
| 7 | ;; This program is free software; you can redistribute it and/or modify | ||
| 8 | ;; it under the terms of the GNU General Public License as published by | ||
| 9 | ;; the Free Software Foundation, either version 3 of the License, or | ||
| 10 | ;; (at your option) any later version. | ||
| 11 | |||
| 12 | ;; This program is distributed in the hope that it will be useful, | ||
| 13 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| 14 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
| 15 | ;; GNU General Public License for more details. | ||
| 16 | |||
| 17 | ;; You should have received a copy of the GNU General Public License | ||
| 18 | ;; along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
| 19 | |||
| 20 | ;;; Commentary: | ||
| 21 | |||
| 22 | ;; This implements the most of CLOS's multiple-dispatch generic functions. | ||
| 23 | ;; To use it you need either (require 'cl-generic) or (require 'cl-lib). | ||
| 24 | ;; The main entry points are: `cl-defgeneric' and `cl-defmethod'. | ||
| 25 | |||
| 26 | ;; Missing elements: | ||
| 27 | ;; - We don't support next-method-p, make-method, call-method, | ||
| 28 | ;; define-method-combination. | ||
| 29 | ;; - Method and generic function objects: CLOS defines methods as objects | ||
| 30 | ;; (same for generic functions), whereas we don't offer such an abstraction. | ||
| 31 | ;; - `no-next-method' should receive the "calling method" object, but since we | ||
| 32 | ;; don't have such a thing, we pass nil instead. | ||
| 33 | ;; - In defgeneric we don't support the options: | ||
| 34 | ;; declare, :method-combination, :generic-function-class, :method-class, | ||
| 35 | ;; :method. | ||
| 36 | ;; Added elements: | ||
| 37 | ;; - We support aliases to generic functions. | ||
| 38 | ;; - The kind of thing on which to dispatch can be extended. | ||
| 39 | ;; There is support in this file for (eql <val>) dispatch as well as dispatch | ||
| 40 | ;; on the type of CL structs, and eieio-core.el adds support for EIEIO | ||
| 41 | ;; defclass objects. | ||
| 42 | |||
| 43 | ;;; Code: | ||
| 44 | |||
| 45 | ;; Note: For generic functions that dispatch on several arguments (i.e. those | ||
| 46 | ;; which use the multiple-dispatch feature), we always use the same "tagcodes" | ||
| 47 | ;; and the same set of arguments on which to dispatch. This works, but is | ||
| 48 | ;; often suboptimal since after one dispatch, the remaining dispatches can | ||
| 49 | ;; usually be simplified, or even completely skipped. | ||
| 50 | |||
| 51 | (eval-when-compile (require 'cl-lib)) | ||
| 52 | (eval-when-compile (require 'pcase)) | ||
| 53 | |||
| 54 | (defvar cl-generic-tagcode-function | ||
| 55 | (lambda (type _name) | ||
| 56 | (if (eq type t) '(0 . 'cl--generic-type) | ||
| 57 | (error "Unknown specializer %S" type))) | ||
| 58 | "Function to get the Elisp code to extract the tag on which we dispatch. | ||
| 59 | Takes a \"parameter-specializer-name\" and a variable name, and returns | ||
| 60 | a pair (PRIORITY . CODE) where CODE is an Elisp expression that should be | ||
| 61 | used to extract the \"tag\" (from the object held in the named variable) | ||
| 62 | that should uniquely determine if we have a match | ||
| 63 | \(i.e. the \"tag\" is the value that will be used to dispatch to the proper | ||
| 64 | method(s)). | ||
| 65 | Such \"tagcodes\" will be or'd together. | ||
| 66 | PRIORITY is an integer from 0 to 100 which is used to sort the tagcodes | ||
| 67 | in the `or'. The higher the priority, the more specific the tag should be. | ||
| 68 | More specifically, if PRIORITY is N and we have two objects X and Y | ||
| 69 | whose tag (according to TAGCODE) is `eql', then it should be the case | ||
| 70 | that for all other (PRIORITY . TAGCODE) where PRIORITY ≤ N, then | ||
| 71 | \(eval TAGCODE) for X is `eql' to (eval TAGCODE) for Y.") | ||
| 72 | |||
| 73 | (defvar cl-generic-tag-types-function | ||
| 74 | (lambda (tag) (if (eq tag 'cl--generic-type) '(t))) | ||
| 75 | "Function to get the list of types that a given \"tag\" matches. | ||
| 76 | They should be sorted from most specific to least specific.") | ||
| 77 | |||
| 78 | (cl-defstruct (cl--generic | ||
| 79 | (:constructor nil) | ||
| 80 | (:constructor cl--generic-make | ||
| 81 | (name &optional dispatches method-table)) | ||
| 82 | (:predicate nil)) | ||
| 83 | (name nil :read-only t) ;Pointer back to the symbol. | ||
| 84 | ;; `dispatches' holds a list of (ARGNUM . TAGCODES) where ARGNUM is the index | ||
| 85 | ;; of the corresponding argument and TAGCODES is a list of (PRIORITY . EXP) | ||
| 86 | ;; where the EXPs are expressions (to be `or'd together) to compute the tag | ||
| 87 | ;; on which to dispatch and PRIORITY is the priority of each expression to | ||
| 88 | ;; decide in which order to sort them. | ||
| 89 | ;; The most important dispatch is last in the list (and the least is first). | ||
| 90 | dispatches | ||
| 91 | ;; `method-table' is a list of | ||
| 92 | ;; ((SPECIALIZERS . QUALIFIER) USES-CNM . FUNCTION), where | ||
| 93 | ;; USES-CNM is a boolean indicating if FUNCTION calls `cl-call-next-method' | ||
| 94 | ;; (and hence expects an extra argument holding the next-method). | ||
| 95 | method-table) | ||
| 96 | |||
| 97 | (defmacro cl--generic (name) | ||
| 98 | `(get ,name 'cl--generic)) | ||
| 99 | |||
| 100 | (defun cl-generic-ensure-function (name) | ||
| 101 | (let (generic | ||
| 102 | (origname name)) | ||
| 103 | (while (and (null (setq generic (cl--generic name))) | ||
| 104 | (fboundp name) | ||
| 105 | (symbolp (symbol-function name))) | ||
| 106 | (setq name (symbol-function name))) | ||
| 107 | (unless (or (not (fboundp name)) | ||
| 108 | (and (functionp name) generic)) | ||
| 109 | (error "%s is already defined as something else than a generic function" | ||
| 110 | origname)) | ||
| 111 | (if generic | ||
| 112 | (cl-assert (eq name (cl--generic-name generic))) | ||
| 113 | (setf (cl--generic name) (setq generic (cl--generic-make name))) | ||
| 114 | (defalias name (cl--generic-make-function generic))) | ||
| 115 | generic)) | ||
| 116 | |||
| 117 | (defun cl--generic-setf-rewrite (name) | ||
| 118 | (let ((setter (intern (format "cl-generic-setter--%s" name)))) | ||
| 119 | (cons setter | ||
| 120 | `(eval-and-compile | ||
| 121 | (unless (eq ',setter (get ',name 'cl-generic-setter)) | ||
| 122 | ;; (when (get ',name 'gv-expander) | ||
| 123 | ;; (error "gv-expander conflicts with (setf %S)" ',name)) | ||
| 124 | (setf (get ',name 'cl-generic-setter) ',setter) | ||
| 125 | (gv-define-setter ,name (val &rest args) | ||
| 126 | (cons ',setter (cons val args)))))))) | ||
| 127 | |||
| 128 | ;;;###autoload | ||
| 129 | (defmacro cl-defgeneric (name args &rest options-and-methods) | ||
| 130 | "Create a generic function NAME. | ||
| 131 | DOC-STRING is the base documentation for this class. A generic | ||
| 132 | function has no body, as its purpose is to decide which method body | ||
| 133 | is appropriate to use. Specific methods are defined with `defmethod'. | ||
| 134 | With this implementation the ARGS are currently ignored. | ||
| 135 | OPTIONS-AND-METHODS is currently only used to specify the docstring, | ||
| 136 | via (:documentation DOCSTRING)." | ||
| 137 | (declare (indent 2) (doc-string 3)) | ||
| 138 | (let* ((docprop (assq :documentation options-and-methods)) | ||
| 139 | (doc (cond ((stringp (car-safe options-and-methods)) | ||
| 140 | (pop options-and-methods)) | ||
| 141 | (docprop | ||
| 142 | (prog1 | ||
| 143 | (cadr docprop) | ||
| 144 | (setq options-and-methods | ||
| 145 | (delq docprop options-and-methods))))))) | ||
| 146 | `(progn | ||
| 147 | ,(when (eq 'setf (car-safe name)) | ||
| 148 | (pcase-let ((`(,setter . ,code) (cl--generic-setf-rewrite | ||
| 149 | (cadr name)))) | ||
| 150 | (setq name setter) | ||
| 151 | code)) | ||
| 152 | (defalias ',name | ||
| 153 | (cl-generic-define ',name ',args ',options-and-methods) | ||
| 154 | ,doc)))) | ||
| 155 | |||
| 156 | (defun cl--generic-mandatory-args (args) | ||
| 157 | (let ((res ())) | ||
| 158 | (while (not (memq (car args) '(nil &rest &optional &key))) | ||
| 159 | (push (pop args) res)) | ||
| 160 | (nreverse res))) | ||
| 161 | |||
| 162 | ;;;###autoload | ||
| 163 | (defun cl-generic-define (name args options-and-methods) | ||
| 164 | (let ((generic (cl-generic-ensure-function name)) | ||
| 165 | (mandatory (cl--generic-mandatory-args args)) | ||
| 166 | (apo (assq :argument-precedence-order options-and-methods))) | ||
| 167 | (setf (cl--generic-dispatches generic) nil) | ||
| 168 | (when apo | ||
| 169 | (dolist (arg (cdr apo)) | ||
| 170 | (let ((pos (memq arg mandatory))) | ||
| 171 | (unless pos (error "%S is not a mandatory argument" arg)) | ||
| 172 | (push (list (- (length mandatory) (length pos))) | ||
| 173 | (cl--generic-dispatches generic))))) | ||
| 174 | (setf (cl--generic-method-table generic) nil) | ||
| 175 | (cl--generic-make-function generic))) | ||
| 176 | |||
| 177 | (defvar cl-generic-current-method-specializers nil | ||
| 178 | ;; This is let-bound during macro-expansion of method bodies, so that those | ||
| 179 | ;; bodies can be optimized knowing that the specializers have matched. | ||
| 180 | ;; FIXME: This presumes the formal arguments aren't modified via `setq' and | ||
| 181 | ;; aren't shadowed either ;-( | ||
| 182 | ;; FIXME: This might leak outside the scope of the method if, during | ||
| 183 | ;; macroexpansion of the method, something causes some other macroexpansion | ||
| 184 | ;; (e.g. an autoload). | ||
| 185 | "List of (VAR . TYPE) where TYPE is var's specializer.") | ||
| 186 | |||
| 187 | (eval-and-compile ;Needed while compiling the cl-defmethod calls below! | ||
| 188 | (defun cl--generic-fgrep (vars sexp) ;Copied from pcase.el. | ||
| 189 | "Check which of the symbols VARS appear in SEXP." | ||
| 190 | (let ((res '())) | ||
| 191 | (while (consp sexp) | ||
| 192 | (dolist (var (cl--generic-fgrep vars (pop sexp))) | ||
| 193 | (unless (memq var res) (push var res)))) | ||
| 194 | (and (memq sexp vars) (not (memq sexp res)) (push sexp res)) | ||
| 195 | res)) | ||
| 196 | |||
| 197 | (defun cl--generic-lambda (args body with-cnm) | ||
| 198 | "Make the lambda expression for a method with ARGS and BODY." | ||
| 199 | (let ((plain-args ()) | ||
| 200 | (cl-generic-current-method-specializers nil) | ||
| 201 | (doc-string (if (stringp (car-safe body)) (pop body))) | ||
| 202 | (mandatory t)) | ||
| 203 | (dolist (arg args) | ||
| 204 | (push (pcase arg | ||
| 205 | ((or '&optional '&rest '&key) (setq mandatory nil) arg) | ||
| 206 | ((and `(,name . ,type) (guard mandatory)) | ||
| 207 | (push (cons name (car type)) | ||
| 208 | cl-generic-current-method-specializers) | ||
| 209 | name) | ||
| 210 | (_ arg)) | ||
| 211 | plain-args)) | ||
| 212 | (setq plain-args (nreverse plain-args)) | ||
| 213 | (let ((fun `(cl-function (lambda ,plain-args | ||
| 214 | ,@(if doc-string (list doc-string)) | ||
| 215 | ,@body)))) | ||
| 216 | (if (not with-cnm) | ||
| 217 | (cons nil fun) | ||
| 218 | ;; First macroexpand away the cl-function stuff (e.g. &key and | ||
| 219 | ;; destructuring args, `declare' and whatnot). | ||
| 220 | (pcase (macroexpand fun macroexpand-all-environment) | ||
| 221 | (`#'(lambda ,args . ,body) | ||
| 222 | (require 'cl-lib) ;Needed to expand `cl-flet'. | ||
| 223 | (let* ((doc-string (and doc-string (stringp (car body)) | ||
| 224 | (pop body))) | ||
| 225 | (cnm (make-symbol "cl--cnm")) | ||
| 226 | (nbody (macroexpand-all | ||
| 227 | `(cl-flet ((cl-call-next-method ,cnm)) | ||
| 228 | ,@body) | ||
| 229 | macroexpand-all-environment)) | ||
| 230 | ;; FIXME: Rather than `grep' after the fact, the | ||
| 231 | ;; macroexpansion should directly set some flag when cnm | ||
| 232 | ;; is used. | ||
| 233 | ;; FIXME: Also, optimize the case where call-next-method is | ||
| 234 | ;; only called with explicit arguments. | ||
| 235 | (uses-cnm (cl--generic-fgrep (list cnm) nbody))) | ||
| 236 | (cons (not (not uses-cnm)) | ||
| 237 | `#'(lambda (,@(if uses-cnm (list cnm)) ,@args) | ||
| 238 | ,@(if doc-string (list doc-string)) | ||
| 239 | ,nbody)))) | ||
| 240 | (f (error "Unexpected macroexpansion result: %S" f)))))))) | ||
| 241 | |||
| 242 | |||
| 243 | ;;;###autoload | ||
| 244 | (defmacro cl-defmethod (name args &rest body) | ||
| 245 | "Define a new method for generic function NAME. | ||
| 246 | I.e. it defines the implementation of NAME to use for invocations where the | ||
| 247 | value of the dispatch argument matches the specified TYPE. | ||
| 248 | The dispatch argument has to be one of the mandatory arguments, and | ||
| 249 | all methods of NAME have to use the same argument for dispatch. | ||
| 250 | The dispatch argument and TYPE are specified in ARGS where the corresponding | ||
| 251 | formal argument appears as (VAR TYPE) rather than just VAR. | ||
| 252 | |||
| 253 | The optional second argument QUALIFIER is a specifier that | ||
| 254 | modifies how the method is combined with other methods, including: | ||
| 255 | :before - Method will be called before the primary | ||
| 256 | :after - Method will be called after the primary | ||
| 257 | :around - Method will be called around everything else | ||
| 258 | The absence of QUALIFIER means this is a \"primary\" method. | ||
| 259 | |||
| 260 | Other than a type, TYPE can also be of the form `(eql VAL)' in | ||
| 261 | which case this method will be invoked when the argument is `eql' to VAL. | ||
| 262 | |||
| 263 | \(fn NAME [QUALIFIER] ARGS &rest [DOCSTRING] BODY)" | ||
| 264 | (declare (doc-string 3) (indent 2)) | ||
| 265 | (let ((qualifiers nil)) | ||
| 266 | (while (keywordp args) | ||
| 267 | (push args qualifiers) | ||
| 268 | (setq args (pop body))) | ||
| 269 | (pcase-let* ((with-cnm (not (memq (car qualifiers) '(:before :after)))) | ||
| 270 | (`(,uses-cnm . ,fun) (cl--generic-lambda args body with-cnm))) | ||
| 271 | `(progn | ||
| 272 | ,(when (eq 'setf (car-safe name)) | ||
| 273 | (pcase-let ((`(,setter . ,code) (cl--generic-setf-rewrite | ||
| 274 | (cadr name)))) | ||
| 275 | (setq name setter) | ||
| 276 | code)) | ||
| 277 | (cl-generic-define-method ',name ',qualifiers ',args | ||
| 278 | ,uses-cnm ,fun))))) | ||
| 279 | |||
| 280 | ;;;###autoload | ||
| 281 | (defun cl-generic-define-method (name qualifiers args uses-cnm function) | ||
| 282 | (when (> (length qualifiers) 1) | ||
| 283 | (error "We only support a single qualifier per method: %S" qualifiers)) | ||
| 284 | (unless (memq (car qualifiers) '(nil :primary :around :after :before)) | ||
| 285 | (error "Unsupported qualifier in: %S" qualifiers)) | ||
| 286 | (let* ((generic (cl-generic-ensure-function name)) | ||
| 287 | (mandatory (cl--generic-mandatory-args args)) | ||
| 288 | (specializers | ||
| 289 | (mapcar (lambda (arg) (if (consp arg) (cadr arg) t)) mandatory)) | ||
| 290 | (key (cons specializers (or (car qualifiers) ':primary))) | ||
| 291 | (mt (cl--generic-method-table generic)) | ||
| 292 | (me (assoc key mt)) | ||
| 293 | (dispatches (cl--generic-dispatches generic)) | ||
| 294 | (i 0)) | ||
| 295 | (dolist (specializer specializers) | ||
| 296 | (let* ((tagcode (funcall cl-generic-tagcode-function specializer 'arg)) | ||
| 297 | (x (assq i dispatches))) | ||
| 298 | (if (not x) | ||
| 299 | (setf (cl--generic-dispatches generic) | ||
| 300 | (setq dispatches (cons (list i tagcode) dispatches))) | ||
| 301 | (unless (member tagcode (cdr x)) | ||
| 302 | (setf (cdr x) | ||
| 303 | (nreverse (sort (cons tagcode (cdr x)) | ||
| 304 | #'car-less-than-car))))) | ||
| 305 | (setq i (1+ i)))) | ||
| 306 | (if me (setcdr me (cons uses-cnm function)) | ||
| 307 | (setf (cl--generic-method-table generic) | ||
| 308 | (cons `(,key ,uses-cnm . ,function) mt)) | ||
| 309 | ;; For aliases, cl--generic-name gives us the actual name. | ||
| 310 | (defalias (cl--generic-name generic) | ||
| 311 | (cl--generic-make-function generic))))) | ||
| 312 | |||
| 313 | (defmacro cl--generic-with-memoization (place &rest code) | ||
| 314 | (declare (indent 1) (debug t)) | ||
| 315 | (gv-letplace (getter setter) place | ||
| 316 | `(or ,getter | ||
| 317 | ,(macroexp-let2 nil val (macroexp-progn code) | ||
| 318 | `(progn | ||
| 319 | ,(funcall setter val) | ||
| 320 | ,val))))) | ||
| 321 | |||
| 322 | (defvar cl--generic-dispatchers (make-hash-table :test #'equal)) | ||
| 323 | |||
| 324 | (defun cl--generic-get-dispatcher (tagcodes dispatch-arg) | ||
| 325 | (cl--generic-with-memoization | ||
| 326 | (gethash (cons dispatch-arg tagcodes) cl--generic-dispatchers) | ||
| 327 | (let ((lexical-binding t) | ||
| 328 | (extraargs ())) | ||
| 329 | (dotimes (_ dispatch-arg) | ||
| 330 | (push (make-symbol "arg") extraargs)) | ||
| 331 | (byte-compile | ||
| 332 | `(lambda (generic dispatches-left) | ||
| 333 | (let ((method-cache (make-hash-table :test #'eql))) | ||
| 334 | (lambda (,@extraargs arg &rest args) | ||
| 335 | (apply (cl--generic-with-memoization | ||
| 336 | (gethash (or ,@(mapcar #'cdr tagcodes)) method-cache) | ||
| 337 | (cl--generic-cache-miss | ||
| 338 | generic ',dispatch-arg dispatches-left | ||
| 339 | (list ,@(mapcar #'cdr tagcodes)))) | ||
| 340 | ,@extraargs arg args)))))))) | ||
| 341 | |||
| 342 | (defun cl--generic-make-function (generic) | ||
| 343 | (let* ((dispatches (cl--generic-dispatches generic)) | ||
| 344 | (dispatch | ||
| 345 | (progn | ||
| 346 | (while (and dispatches | ||
| 347 | (member (cdar dispatches) | ||
| 348 | '(nil ((0 . 'cl--generic-type))))) | ||
| 349 | (setq dispatches (cdr dispatches))) | ||
| 350 | (pop dispatches)))) | ||
| 351 | (if (null dispatch) | ||
| 352 | (cl--generic-build-combined-method | ||
| 353 | (cl--generic-name generic) | ||
| 354 | (cl--generic-method-table generic)) | ||
| 355 | (let ((dispatcher (cl--generic-get-dispatcher | ||
| 356 | (cdr dispatch) (car dispatch)))) | ||
| 357 | (funcall dispatcher generic dispatches))))) | ||
| 358 | |||
| 359 | (defun cl--generic-nest (fun methods) | ||
| 360 | (pcase-dolist (`(,uses-cnm . ,method) methods) | ||
| 361 | (setq fun | ||
| 362 | (if (not uses-cnm) method | ||
| 363 | (let ((next fun)) | ||
| 364 | (lambda (&rest args) | ||
| 365 | (apply method | ||
| 366 | ;; FIXME: This sucks: passing just `next' would | ||
| 367 | ;; be a lot more efficient than the lambda+apply | ||
| 368 | ;; quasi-η, but we need this to implement the | ||
| 369 | ;; "if call-next-method is called with no | ||
| 370 | ;; arguments, then use the previous arguments". | ||
| 371 | (lambda (&rest cnm-args) | ||
| 372 | (apply next (or cnm-args args))) | ||
| 373 | args)))))) | ||
| 374 | fun) | ||
| 375 | |||
| 376 | (defvar cl--generic-combined-method-memoization | ||
| 377 | (make-hash-table :test #'equal :weakness 'value) | ||
| 378 | "Table storing previously built combined-methods. | ||
| 379 | This is particularly useful when many different tags select the same set | ||
| 380 | of methods, since this table then allows us to share a single combined-method | ||
| 381 | for all those different tags in the method-cache.") | ||
| 382 | |||
| 383 | (defun cl--generic-build-combined-method (generic-name methods) | ||
| 384 | (let ((mets-by-qual ())) | ||
| 385 | (dolist (qm methods) | ||
| 386 | (push (cdr qm) (alist-get (cdar qm) mets-by-qual))) | ||
| 387 | (cl--generic-with-memoization | ||
| 388 | (gethash (cons generic-name mets-by-qual) | ||
| 389 | cl--generic-combined-method-memoization) | ||
| 390 | (cond | ||
| 391 | ((null mets-by-qual) (lambda (&rest args) | ||
| 392 | (cl-no-applicable-method generic-name args))) | ||
| 393 | (t | ||
| 394 | (let* ((fun (lambda (&rest args) | ||
| 395 | ;; FIXME: CLOS passes as second arg the "calling method". | ||
| 396 | ;; We don't currently have "method objects" like CLOS | ||
| 397 | ;; does so we can't really do it the CLOS way. | ||
| 398 | ;; The closest would be to pass the lambda corresponding | ||
| 399 | ;; to the method, but the caller wouldn't be able to do | ||
| 400 | ;; much with it anyway. So we pass nil for now. | ||
| 401 | (apply #'cl-no-next-method generic-name nil args))) | ||
| 402 | ;; We use `cdr' to drop the `uses-cnm' annotations. | ||
| 403 | (before | ||
| 404 | (mapcar #'cdr (reverse (alist-get :before mets-by-qual)))) | ||
| 405 | (after (mapcar #'cdr (alist-get :after mets-by-qual)))) | ||
| 406 | (setq fun (cl--generic-nest fun (alist-get :primary mets-by-qual))) | ||
| 407 | (when (or after before) | ||
| 408 | (let ((next fun)) | ||
| 409 | (setq fun (lambda (&rest args) | ||
| 410 | (dolist (bf before) | ||
| 411 | (apply bf args)) | ||
| 412 | (apply next args) | ||
| 413 | (dolist (af after) | ||
| 414 | (apply af args)))))) | ||
| 415 | (cl--generic-nest fun (alist-get :around mets-by-qual)))))))) | ||
| 416 | |||
| 417 | (defun cl--generic-cache-miss (generic dispatch-arg dispatches-left tags) | ||
| 418 | (let ((types (apply #'append (mapcar cl-generic-tag-types-function tags))) | ||
| 419 | (methods '())) | ||
| 420 | (dolist (method-desc (cl--generic-method-table generic)) | ||
| 421 | (let ((m (member (nth dispatch-arg (caar method-desc)) types))) | ||
| 422 | (when m | ||
| 423 | (push (cons (length m) method-desc) methods)))) | ||
| 424 | ;; Sort the methods, most specific first. | ||
| 425 | ;; It would be tempting to sort them once and for all in the method-table | ||
| 426 | ;; rather than here, but the order might depend on the actual argument | ||
| 427 | ;; (e.g. for multiple inheritance with defclass). | ||
| 428 | (setq methods (nreverse (mapcar #'cdr (sort methods #'car-less-than-car)))) | ||
| 429 | (cl--generic-make-function (cl--generic-make (cl--generic-name generic) | ||
| 430 | dispatches-left methods)))) | ||
| 431 | |||
| 432 | ;;; Define some pre-defined generic functions, used internally. | ||
| 433 | |||
| 434 | (define-error 'cl-no-method "No method for %S") | ||
| 435 | (define-error 'cl-no-next-method "No next method for %S" 'cl-no-method) | ||
| 436 | (define-error 'cl-no-applicable-method "No applicable method for %S" | ||
| 437 | 'cl-no-method) | ||
| 438 | |||
| 439 | (cl-defgeneric cl-no-next-method (generic method &rest args) | ||
| 440 | "Function called when `cl-call-next-method' finds no next method.") | ||
| 441 | (cl-defmethod cl-no-next-method ((generic t) method &rest args) | ||
| 442 | (signal 'cl-no-next-method `(,generic ,method ,@args))) | ||
| 443 | |||
| 444 | (cl-defgeneric cl-no-applicable-method (generic &rest args) | ||
| 445 | "Function called when a method call finds no applicable method.") | ||
| 446 | (cl-defmethod cl-no-applicable-method ((generic t) &rest args) | ||
| 447 | (signal 'cl-no-applicable-method `(,generic ,@args))) | ||
| 448 | |||
| 449 | (defun cl-call-next-method (&rest _args) | ||
| 450 | "Function to call the next applicable method. | ||
| 451 | Can only be used from within the lexical body of a primary or around method." | ||
| 452 | (error "cl-call-next-method only allowed inside primary and around methods")) | ||
| 453 | |||
| 454 | ;;; Add support for describe-function | ||
| 455 | |||
| 456 | (add-hook 'help-fns-describe-function-functions 'cl--generic-describe) | ||
| 457 | (defun cl--generic-describe (function) | ||
| 458 | ;; FIXME: Fix up the main "in `<file>'" hyperlink, and add such hyperlinks | ||
| 459 | ;; for each method. | ||
| 460 | (let ((generic (if (symbolp function) (cl--generic function)))) | ||
| 461 | (when generic | ||
| 462 | (save-excursion | ||
| 463 | (insert "\n\nThis is a generic function.\n\n") | ||
| 464 | (insert (propertize "Implementations:\n\n" 'face 'bold)) | ||
| 465 | ;; Loop over fanciful generics | ||
| 466 | (pcase-dolist (`((,type . ,qualifier) . ,method) | ||
| 467 | (cl--generic-method-table generic)) | ||
| 468 | (insert "`") | ||
| 469 | (if (symbolp type) | ||
| 470 | ;; FIXME: Add support for cl-structs in help-variable. | ||
| 471 | (help-insert-xref-button (symbol-name type) | ||
| 472 | 'help-variable type) | ||
| 473 | (insert (format "%S" type))) | ||
| 474 | (insert (format "' %S %S\n" | ||
| 475 | (car qualifier) | ||
| 476 | (let ((args (help-function-arglist method))) | ||
| 477 | ;; Drop cl--generic-next arg if present. | ||
| 478 | (if (memq (car qualifier) '(:after :before)) | ||
| 479 | args (cdr args))))) | ||
| 480 | (insert (or (documentation method) "Undocumented") "\n\n")))))) | ||
| 481 | |||
| 482 | ;;; Support for (eql <val>) specializers. | ||
| 483 | |||
| 484 | (defvar cl--generic-eql-used (make-hash-table :test #'eql)) | ||
| 485 | |||
| 486 | (add-function :before-until cl-generic-tagcode-function | ||
| 487 | #'cl--generic-eql-tagcode) | ||
| 488 | (defun cl--generic-eql-tagcode (type name) | ||
| 489 | (when (eq (car-safe type) 'eql) | ||
| 490 | (puthash (cadr type) type cl--generic-eql-used) | ||
| 491 | `(100 . (gethash ,name cl--generic-eql-used)))) | ||
| 492 | |||
| 493 | (add-function :before-until cl-generic-tag-types-function | ||
| 494 | #'cl--generic-eql-tag-types) | ||
| 495 | (defun cl--generic-eql-tag-types (tag) | ||
| 496 | (if (eq (car-safe tag) 'eql) (list tag))) | ||
| 497 | |||
| 498 | ;;; Support for cl-defstructs specializers. | ||
| 499 | |||
| 500 | (add-function :before-until cl-generic-tagcode-function | ||
| 501 | #'cl--generic-struct-tagcode) | ||
| 502 | (defun cl--generic-struct-tagcode (type name) | ||
| 503 | (and (symbolp type) | ||
| 504 | (get type 'cl-struct-type) | ||
| 505 | (or (eq 'vector (car (get type 'cl-struct-type))) | ||
| 506 | (error "Can't dispatch on cl-struct %S: type is %S" | ||
| 507 | type (car (get type 'cl-struct-type)))) | ||
| 508 | (or (equal '(cl-tag-slot) (car (get type 'cl-struct-slots))) | ||
| 509 | (error "Can't dispatch on cl-struct %S: no tag in slot 0" | ||
| 510 | type)) | ||
| 511 | ;; We could/should check the vector has length >0, | ||
| 512 | ;; but really, mixing vectors and structs is a bad idea, | ||
| 513 | ;; so let's not waste time trying to handle the case | ||
| 514 | ;; of an empty vector. | ||
| 515 | ;; BEWARE: this returns a bogus tag for non-struct vectors. | ||
| 516 | `(50 . (and (vectorp ,name) (aref ,name 0))))) | ||
| 517 | |||
| 518 | (add-function :before-until cl-generic-tag-types-function | ||
| 519 | #'cl--generic-struct-tag-types) | ||
| 520 | (defun cl--generic-struct-tag-types (tag) | ||
| 521 | ;; FIXME: cl-defstruct doesn't make it easy for us. | ||
| 522 | (and (symbolp tag) | ||
| 523 | ;; A method call shouldn't itself mess with the match-data. | ||
| 524 | (string-match-p "\\`cl-struct-\\(.*\\)" (symbol-name tag)) | ||
| 525 | (let ((types (list (intern (substring (symbol-name tag) 10))))) | ||
| 526 | (while (get (car types) 'cl-struct-include) | ||
| 527 | (push (get (car types) 'cl-struct-include) types)) | ||
| 528 | (push 'cl-struct types) ;The "parent type" of all cl-structs. | ||
| 529 | (nreverse types)))) | ||
| 530 | |||
| 531 | ;;; Dispatch on "old-style types". | ||
| 532 | |||
| 533 | (defconst cl--generic-typeof-types | ||
| 534 | ;; Hand made from the source code of `type-of'. | ||
| 535 | '((integer number) (symbol) (string array) (cons list) | ||
| 536 | ;; Markers aren't `numberp', yet they are accepted wherever integers are | ||
| 537 | ;; accepted, pretty much. | ||
| 538 | (marker) (overlay) (float number) (window-configuration) | ||
| 539 | (process) (window) (subr) (compiled-function) (buffer) (char-table array) | ||
| 540 | (bool-vector array) | ||
| 541 | (frame) (hash-table) (font-spec) (font-entity) (font-object) | ||
| 542 | (vector array) | ||
| 543 | ;; Plus, hand made: | ||
| 544 | (null list symbol) | ||
| 545 | (list) | ||
| 546 | (array) | ||
| 547 | (number))) | ||
| 548 | |||
| 549 | (add-function :before-until cl-generic-tagcode-function | ||
| 550 | #'cl--generic-typeof-tagcode) | ||
| 551 | (defun cl--generic-typeof-tagcode (type name) | ||
| 552 | ;; FIXME: Add support for other types accepted by `cl-typep' such | ||
| 553 | ;; as `character', `atom', `face', `function', ... | ||
| 554 | (and (assq type cl--generic-typeof-types) | ||
| 555 | (progn | ||
| 556 | (if (memq type '(vector array)) | ||
| 557 | (message "`%S' also matches CL structs and EIEIO classes" type)) | ||
| 558 | ;; FIXME: We could also change `type-of' to return `null' for nil. | ||
| 559 | `(10 . (if ,name (type-of ,name) 'null))))) | ||
| 560 | |||
| 561 | (add-function :before-until cl-generic-tag-types-function | ||
| 562 | #'cl--generic-typeof-types) | ||
| 563 | (defun cl--generic-typeof-types (tag) | ||
| 564 | (and (symbolp tag) | ||
| 565 | (assq tag cl--generic-typeof-types))) | ||
| 566 | |||
| 567 | ;;; Just for kicks: dispatch on major-mode | ||
| 568 | ;; | ||
| 569 | ;; Here's how you'd use it: | ||
| 570 | ;; (cl-defmethod foo ((x (major-mode text-mode)) y z) ...) | ||
| 571 | ;; And then | ||
| 572 | ;; (foo 'major-mode toto titi) | ||
| 573 | ;; | ||
| 574 | ;; FIXME: Better would be to do that via dispatch on an "implicit argument". | ||
| 575 | |||
| 576 | ;; (defvar cl--generic-major-modes (make-hash-table :test #'eq)) | ||
| 577 | ;; | ||
| 578 | ;; (add-function :before-until cl-generic-tagcode-function | ||
| 579 | ;; #'cl--generic-major-mode-tagcode) | ||
| 580 | ;; (defun cl--generic-major-mode-tagcode (type name) | ||
| 581 | ;; (if (eq 'major-mode (car-safe type)) | ||
| 582 | ;; `(50 . (if (eq ,name 'major-mode) | ||
| 583 | ;; (cl--generic-with-memoization | ||
| 584 | ;; (gethash major-mode cl--generic-major-modes) | ||
| 585 | ;; `(cl--generic-major-mode . ,major-mode)))))) | ||
| 586 | ;; | ||
| 587 | ;; (add-function :before-until cl-generic-tag-types-function | ||
| 588 | ;; #'cl--generic-major-mode-types) | ||
| 589 | ;; (defun cl--generic-major-mode-types (tag) | ||
| 590 | ;; (when (eq (car-safe tag) 'cl--generic-major-mode) | ||
| 591 | ;; (if (eq tag 'fundamental-mode) '(fundamental-mode t) | ||
| 592 | ;; (let ((types `((major-mode ,(cdr tag))))) | ||
| 593 | ;; (while (get (car types) 'derived-mode-parent) | ||
| 594 | ;; (push (list 'major-mode (get (car types) 'derived-mode-parent)) | ||
| 595 | ;; types)) | ||
| 596 | ;; (unless (eq 'fundamental-mode (car types)) | ||
| 597 | ;; (push '(major-mode fundamental-mode) types)) | ||
| 598 | ;; (nreverse types))))) | ||
| 599 | |||
| 600 | ;; Local variables: | ||
| 601 | ;; generated-autoload-file: "cl-loaddefs.el" | ||
| 602 | ;; End: | ||
| 603 | |||
| 604 | (provide 'cl-generic) | ||
| 605 | ;;; cl-generic.el ends here | ||
diff --git a/lisp/emacs-lisp/cl-macs.el b/lisp/emacs-lisp/cl-macs.el index fff5b27315c..0070599af6f 100644 --- a/lisp/emacs-lisp/cl-macs.el +++ b/lisp/emacs-lisp/cl-macs.el | |||
| @@ -625,14 +625,20 @@ The result of the body appears to the compiler as a quoted constant." | |||
| 625 | (set `(setq ,temp ,form))) | 625 | (set `(setq ,temp ,form))) |
| 626 | (if (and (fboundp 'byte-compile-file-form-defmumble) | 626 | (if (and (fboundp 'byte-compile-file-form-defmumble) |
| 627 | (boundp 'this-kind) (boundp 'that-one)) | 627 | (boundp 'this-kind) (boundp 'that-one)) |
| 628 | (fset 'byte-compile-file-form | 628 | ;; Else, we can't output right away, so we have to delay it to the |
| 629 | `(lambda (form) | 629 | ;; next time we're at the top-level. |
| 630 | (fset 'byte-compile-file-form | 630 | ;; FIXME: Use advice-add/remove. |
| 631 | ',(symbol-function 'byte-compile-file-form)) | 631 | (fset 'byte-compile-file-form |
| 632 | (byte-compile-file-form ',set) | 632 | (let ((old (symbol-function 'byte-compile-file-form))) |
| 633 | (byte-compile-file-form form))) | 633 | (lambda (form) |
| 634 | (print set (symbol-value 'byte-compile--outbuffer))) | 634 | (fset 'byte-compile-file-form old) |
| 635 | `(symbol-value ',temp)) | 635 | (byte-compile-file-form set) |
| 636 | (byte-compile-file-form form)))) | ||
| 637 | ;; If we're not in the middle of compiling something, we can | ||
| 638 | ;; output directly to byte-compile-outbuffer, to make sure | ||
| 639 | ;; temp is set before we use it. | ||
| 640 | (print set byte-compile--outbuffer)) | ||
| 641 | temp) | ||
| 636 | `',(eval form))) | 642 | `',(eval form))) |
| 637 | 643 | ||
| 638 | 644 | ||
| @@ -1824,18 +1830,30 @@ a `let' form, except that the list of symbols can be computed at run-time." | |||
| 1824 | (defmacro cl-flet (bindings &rest body) | 1830 | (defmacro cl-flet (bindings &rest body) |
| 1825 | "Make local function definitions. | 1831 | "Make local function definitions. |
| 1826 | Like `cl-labels' but the definitions are not recursive. | 1832 | Like `cl-labels' but the definitions are not recursive. |
| 1833 | Each binding can take the form (FUNC EXP) where | ||
| 1834 | FUNC is the function name, and EXP is an expression that returns the | ||
| 1835 | function value to which it should be bound, or it can take the more common | ||
| 1836 | form \(FUNC ARGLIST BODY...) which is a shorthand | ||
| 1837 | for (FUNC (lambda ARGLIST BODY)). | ||
| 1827 | 1838 | ||
| 1828 | \(fn ((FUNC ARGLIST BODY...) ...) FORM...)" | 1839 | \(fn ((FUNC ARGLIST BODY...) ...) FORM...)" |
| 1829 | (declare (indent 1) (debug ((&rest (cl-defun)) cl-declarations body))) | 1840 | (declare (indent 1) (debug ((&rest (cl-defun)) cl-declarations body))) |
| 1830 | (let ((binds ()) (newenv macroexpand-all-environment)) | 1841 | (let ((binds ()) (newenv macroexpand-all-environment)) |
| 1831 | (dolist (binding bindings) | 1842 | (dolist (binding bindings) |
| 1832 | (let ((var (make-symbol (format "--cl-%s--" (car binding))))) | 1843 | (let ((var (make-symbol (format "--cl-%s--" (car binding)))) |
| 1833 | (push (list var `(cl-function (lambda . ,(cdr binding)))) binds) | 1844 | (args-and-body (cdr binding))) |
| 1845 | (if (and (= (length args-and-body) 1) (symbolp (car args-and-body))) | ||
| 1846 | ;; Optimize (cl-flet ((fun var)) body). | ||
| 1847 | (setq var (car args-and-body)) | ||
| 1848 | (push (list var (if (= (length args-and-body) 1) | ||
| 1849 | (car args-and-body) | ||
| 1850 | `(cl-function (lambda . ,args-and-body)))) | ||
| 1851 | binds)) | ||
| 1834 | (push (cons (car binding) | 1852 | (push (cons (car binding) |
| 1835 | `(lambda (&rest cl-labels-args) | 1853 | (lambda (&rest cl-labels-args) |
| 1836 | (cl-list* 'funcall ',var | 1854 | (cl-list* 'funcall var cl-labels-args))) |
| 1837 | cl-labels-args))) | ||
| 1838 | newenv))) | 1855 | newenv))) |
| 1856 | ;; FIXME: Eliminate those functions which aren't referenced. | ||
| 1839 | `(let ,(nreverse binds) | 1857 | `(let ,(nreverse binds) |
| 1840 | ,@(macroexp-unprogn | 1858 | ,@(macroexp-unprogn |
| 1841 | (macroexpand-all | 1859 | (macroexpand-all |
| @@ -1869,9 +1887,8 @@ in closures will only work if `lexical-binding' is in use. | |||
| 1869 | (let ((var (make-symbol (format "--cl-%s--" (car binding))))) | 1887 | (let ((var (make-symbol (format "--cl-%s--" (car binding))))) |
| 1870 | (push (list var `(cl-function (lambda . ,(cdr binding)))) binds) | 1888 | (push (list var `(cl-function (lambda . ,(cdr binding)))) binds) |
| 1871 | (push (cons (car binding) | 1889 | (push (cons (car binding) |
| 1872 | `(lambda (&rest cl-labels-args) | 1890 | (lambda (&rest cl-labels-args) |
| 1873 | (cl-list* 'funcall ',var | 1891 | (cl-list* 'funcall var cl-labels-args))) |
| 1874 | cl-labels-args))) | ||
| 1875 | newenv))) | 1892 | newenv))) |
| 1876 | (macroexpand-all `(letrec ,(nreverse binds) ,@body) | 1893 | (macroexpand-all `(letrec ,(nreverse binds) ,@body) |
| 1877 | ;; Don't override lexical-let's macro-expander. | 1894 | ;; Don't override lexical-let's macro-expander. |
| @@ -1898,7 +1915,8 @@ This is like `cl-flet', but for macros instead of functions. | |||
| 1898 | (res (cl--transform-lambda (cdar bindings) name))) | 1915 | (res (cl--transform-lambda (cdar bindings) name))) |
| 1899 | (eval (car res)) | 1916 | (eval (car res)) |
| 1900 | (macroexpand-all (macroexp-progn body) | 1917 | (macroexpand-all (macroexp-progn body) |
| 1901 | (cons (cons name `(lambda ,@(cdr res))) | 1918 | (cons (cons name |
| 1919 | (eval `(cl-function (lambda ,@(cdr res))) t)) | ||
| 1902 | macroexpand-all-environment)))))) | 1920 | macroexpand-all-environment)))))) |
| 1903 | 1921 | ||
| 1904 | (defconst cl--old-macroexpand | 1922 | (defconst cl--old-macroexpand |
diff --git a/test/ChangeLog b/test/ChangeLog index 83bb8bf00c7..211a06c2cbd 100644 --- a/test/ChangeLog +++ b/test/ChangeLog | |||
| @@ -1,3 +1,7 @@ | |||
| 1 | 2015-01-14 Stefan Monnier <monnier@iro.umontreal.ca> | ||
| 2 | |||
| 3 | * automated/cl-generic-tests.el: New file. | ||
| 4 | |||
| 1 | 2015-01-08 Stefan Monnier <monnier@iro.umontreal.ca> | 5 | 2015-01-08 Stefan Monnier <monnier@iro.umontreal.ca> |
| 2 | 6 | ||
| 3 | * automated/eieio-tests.el (eieio-test-23-inheritance-check): Don't use | 7 | * automated/eieio-tests.el (eieio-test-23-inheritance-check): Don't use |
diff --git a/test/automated/cl-generic-tests.el b/test/automated/cl-generic-tests.el new file mode 100644 index 00000000000..5c5e5d1c7ce --- /dev/null +++ b/test/automated/cl-generic-tests.el | |||
| @@ -0,0 +1,131 @@ | |||
| 1 | ;;; cl-generic-tests.el --- Tests for cl-generic.el functionality -*- lexical-binding: t; -*- | ||
| 2 | |||
| 3 | ;; Copyright (C) 2015 Stefan Monnier | ||
| 4 | |||
| 5 | ;; Author: Stefan Monnier <monnier@iro.umontreal.ca> | ||
| 6 | |||
| 7 | ;; This program is free software; you can redistribute it and/or modify | ||
| 8 | ;; it under the terms of the GNU General Public License as published by | ||
| 9 | ;; the Free Software Foundation, either version 3 of the License, or | ||
| 10 | ;; (at your option) any later version. | ||
| 11 | |||
| 12 | ;; This program is distributed in the hope that it will be useful, | ||
| 13 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| 14 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
| 15 | ;; GNU General Public License for more details. | ||
| 16 | |||
| 17 | ;; You should have received a copy of the GNU General Public License | ||
| 18 | ;; along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
| 19 | |||
| 20 | ;;; Commentary: | ||
| 21 | |||
| 22 | ;;; Code: | ||
| 23 | |||
| 24 | (require 'ert) | ||
| 25 | (require 'cl-lib) | ||
| 26 | |||
| 27 | (cl-defgeneric cl--generic-1 (x y)) | ||
| 28 | (cl-defgeneric (setf cl--generic-1) (v y z) "My generic doc.") | ||
| 29 | |||
| 30 | (ert-deftest cl-generic-test-0 () | ||
| 31 | (cl-defgeneric cl--generic-1 (x y)) | ||
| 32 | (cl-defmethod cl--generic-1 ((x t) y) (cons x y)) | ||
| 33 | (should (equal (cl--generic-1 'a 'b) '(a . b)))) | ||
| 34 | |||
| 35 | (ert-deftest cl-generic-test-1-eql () | ||
| 36 | (cl-defgeneric cl--generic-1 (x y)) | ||
| 37 | (cl-defmethod cl--generic-1 ((x t) y) (cons x y)) | ||
| 38 | (cl-defmethod cl--generic-1 ((_x (eql 4)) _y) | ||
| 39 | (cons "quatre" (cl-call-next-method))) | ||
| 40 | (cl-defmethod cl--generic-1 ((_x (eql 5)) _y) | ||
| 41 | (cons "cinq" (cl-call-next-method))) | ||
| 42 | (cl-defmethod cl--generic-1 ((_x (eql 6)) y) | ||
| 43 | (cons "six" (cl-call-next-method 'a y))) | ||
| 44 | (should (equal (cl--generic-1 'a nil) '(a))) | ||
| 45 | (should (equal (cl--generic-1 4 nil) '("quatre" 4))) | ||
| 46 | (should (equal (cl--generic-1 5 nil) '("cinq" 5))) | ||
| 47 | (should (equal (cl--generic-1 6 nil) '("six" a)))) | ||
| 48 | |||
| 49 | (cl-defstruct cl-generic-struct-parent a b) | ||
| 50 | (cl-defstruct (cl-generic-struct-child1 (:include cl-generic-struct-parent)) c) | ||
| 51 | (cl-defstruct (cl-generic-struct-child11 (:include cl-generic-struct-child1)) d) | ||
| 52 | (cl-defstruct (cl-generic-struct-child2 (:include cl-generic-struct-parent)) e) | ||
| 53 | |||
| 54 | (ert-deftest cl-generic-test-2-struct () | ||
| 55 | (cl-defgeneric cl--generic-1 (x y) "My doc.") | ||
| 56 | (cl-defmethod cl--generic-1 ((x t) y) "Doc 1." (cons x y)) | ||
| 57 | (cl-defmethod cl--generic-1 ((_x cl-generic-struct-parent) y) | ||
| 58 | "Doc 2." (cons "parent" (cl-call-next-method 'a y))) | ||
| 59 | (cl-defmethod cl--generic-1 ((_x cl-generic-struct-child1) _y) | ||
| 60 | (cons "child1" (cl-call-next-method))) | ||
| 61 | (cl-defmethod cl--generic-1 :around ((_x t) _y) | ||
| 62 | (cons "around" (cl-call-next-method))) | ||
| 63 | (cl-defmethod cl--generic-1 :around ((_x cl-generic-struct-child11) _y) | ||
| 64 | (cons "child11" (cl-call-next-method))) | ||
| 65 | (cl-defmethod cl--generic-1 ((_x cl-generic-struct-child2) _y) | ||
| 66 | (cons "child2" (cl-call-next-method))) | ||
| 67 | (should (equal (cl--generic-1 (make-cl-generic-struct-child1) nil) | ||
| 68 | '("around" "child1" "parent" a))) | ||
| 69 | (should (equal (cl--generic-1 (make-cl-generic-struct-child2) nil) | ||
| 70 | '("around""child2" "parent" a))) | ||
| 71 | (should (equal (cl--generic-1 (make-cl-generic-struct-child11) nil) | ||
| 72 | '("child11" "around""child1" "parent" a)))) | ||
| 73 | |||
| 74 | (ert-deftest cl-generic-test-3-setf () | ||
| 75 | (cl-defmethod (setf cl--generic-1) (v (y t) z) (list v y z)) | ||
| 76 | (cl-defmethod (setf cl--generic-1) (v (_y (eql 4)) z) (list v "four" z)) | ||
| 77 | (should (equal (setf (cl--generic-1 'a 'b) 'v) '(v a b))) | ||
| 78 | (should (equal (setf (cl--generic-1 4 'b) 'v) '(v "four" b))) | ||
| 79 | (let ((x ())) | ||
| 80 | (should (equal (setf (cl--generic-1 (progn (push 1 x) 'a) | ||
| 81 | (progn (push 2 x) 'b)) | ||
| 82 | (progn (push 3 x) 'v)) | ||
| 83 | '(v a b))) | ||
| 84 | (should (equal x '(3 2 1))))) | ||
| 85 | |||
| 86 | (ert-deftest cl-generic-test-4-overlapping-tagcodes () | ||
| 87 | (cl-defgeneric cl--generic-1 (x y) "My doc.") | ||
| 88 | (cl-defmethod cl--generic-1 ((y t) z) (list y z)) | ||
| 89 | (cl-defmethod cl--generic-1 ((_y (eql 4)) _z) | ||
| 90 | (cons "four" (cl-call-next-method))) | ||
| 91 | (cl-defmethod cl--generic-1 ((_y integer) _z) | ||
| 92 | (cons "integer" (cl-call-next-method))) | ||
| 93 | (cl-defmethod cl--generic-1 ((_y number) _z) | ||
| 94 | (cons "number" (cl-call-next-method))) | ||
| 95 | (should (equal (cl--generic-1 'a 'b) '(a b))) | ||
| 96 | (should (equal (cl--generic-1 1 'b) '("integer" "number" 1 b))) | ||
| 97 | (should (equal (cl--generic-1 4 'b) '("four" "integer" "number" 4 b)))) | ||
| 98 | |||
| 99 | (ert-deftest cl-generic-test-5-alias () | ||
| 100 | (cl-defgeneric cl--generic-1 (x y) "My doc.") | ||
| 101 | (defalias 'cl--generic-2 #'cl--generic-1) | ||
| 102 | (cl-defmethod cl--generic-1 ((y t) z) (list y z)) | ||
| 103 | (cl-defmethod cl--generic-2 ((_y (eql 4)) _z) | ||
| 104 | (cons "four" (cl-call-next-method))) | ||
| 105 | (should (equal (cl--generic-1 4 'b) '("four" 4 b)))) | ||
| 106 | |||
| 107 | (ert-deftest cl-generic-test-6-multiple-dispatch () | ||
| 108 | (cl-defgeneric cl--generic-1 (x y) "My doc.") | ||
| 109 | (cl-defmethod cl--generic-1 (x y) (list x y)) | ||
| 110 | (cl-defmethod cl--generic-1 (_x (_y integer)) | ||
| 111 | (cons "y-int" (cl-call-next-method))) | ||
| 112 | (cl-defmethod cl--generic-1 ((_x integer) _y) | ||
| 113 | (cons "x-int" (cl-call-next-method))) | ||
| 114 | (cl-defmethod cl--generic-1 ((_x integer) (_y integer)) | ||
| 115 | (cons "x&y-int" (cl-call-next-method))) | ||
| 116 | (should (equal (cl--generic-1 1 2) '("x&y-int" "x-int" "y-int" 1 2)))) | ||
| 117 | |||
| 118 | (ert-deftest cl-generic-test-7-apo () | ||
| 119 | (cl-defgeneric cl--generic-1 (x y) | ||
| 120 | (:documentation "My doc.") (:argument-precedence-order y x)) | ||
| 121 | (cl-defmethod cl--generic-1 (x y) (list x y)) | ||
| 122 | (cl-defmethod cl--generic-1 (_x (_y integer)) | ||
| 123 | (cons "y-int" (cl-call-next-method))) | ||
| 124 | (cl-defmethod cl--generic-1 ((_x integer) _y) | ||
| 125 | (cons "x-int" (cl-call-next-method))) | ||
| 126 | (cl-defmethod cl--generic-1 ((_x integer) (_y integer)) | ||
| 127 | (cons "x&y-int" (cl-call-next-method))) | ||
| 128 | (should (equal (cl--generic-1 1 2) '("x&y-int" "y-int" "x-int" 1 2)))) | ||
| 129 | |||
| 130 | (provide 'cl-generic-tests) | ||
| 131 | ;;; cl-generic-tests.el ends here | ||