aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorStefan Monnier2015-05-12 00:10:38 -0400
committerStefan Monnier2015-05-12 00:10:38 -0400
commitd1b74200dad00cea845037064dc8b5d50db35dd2 (patch)
tree51102ad11a51f14177fa58dc601a536cac40d0ca
parentf0352ebdf088bea19b44ddb31e94888bc2345a24 (diff)
downloademacs-d1b74200dad00cea845037064dc8b5d50db35dd2.tar.gz
emacs-d1b74200dad00cea845037064dc8b5d50db35dd2.zip
* lisp/emacs-lisp/cl-generic.el: Add dispatch on &context arguments
(cl--generic-mandatory-args): Remove. (cl--generic-split-args): New function. (cl-generic-define, cl--generic-lambda): Use it. (cl-generic-define-method): Use it as well, and add support for context args. (cl--generic-get-dispatcher): Handle &context dispatch. (cl--generic-cache-miss): `dispatch-arg' can now be a context expression. (cl--generic-dispatchers): Pre-fill. * test/automated/cl-generic-tests.el (sm-generic-test-12-context): New test.
-rw-r--r--lisp/emacs-lisp/cl-generic.el244
-rw-r--r--test/automated/cl-generic-tests.el10
2 files changed, 140 insertions, 114 deletions
diff --git a/lisp/emacs-lisp/cl-generic.el b/lisp/emacs-lisp/cl-generic.el
index fb11a3e25a1..f6595d3035b 100644
--- a/lisp/emacs-lisp/cl-generic.el
+++ b/lisp/emacs-lisp/cl-generic.el
@@ -54,6 +54,15 @@
54;; - The standard method combination supports ":extra STRING" qualifiers 54;; - The standard method combination supports ":extra STRING" qualifiers
55;; which simply allows adding more methods for the same 55;; which simply allows adding more methods for the same
56;; specializers&qualifiers. 56;; specializers&qualifiers.
57;; - Methods can dispatch on the context. For that, a method needs to specify
58;; context arguments, introduced by `&context' (which need to come right
59;; after the mandatory arguments and before anything like
60;; &optional/&rest/&key). Each context argument is given as (EXP SPECIALIZER)
61;; which means that EXP is taken as an expression which computes some context
62;; and this value is then used to dispatch.
63;; E.g. (foo &context (major-mode (eql c-mode))) is an arglist specifying
64;; that this method will only be applicable when `major-mode' has value
65;; `c-mode'.
57 66
58;; Efficiency considerations: overall, I've made an effort to make this fairly 67;; Efficiency considerations: overall, I've made an effort to make this fairly
59;; efficient for the expected case (e.g. no constant redefinition of methods). 68;; efficient for the expected case (e.g. no constant redefinition of methods).
@@ -222,17 +231,12 @@ BODY, if present, is used as the body of a default method.
222 ,@(mapcar (lambda (method) `(cl-defmethod ,name ,@method)) 231 ,@(mapcar (lambda (method) `(cl-defmethod ,name ,@method))
223 (nreverse methods))))) 232 (nreverse methods)))))
224 233
225(defun cl--generic-mandatory-args (args)
226 (let ((res ()))
227 (while (not (memq (car args) '(nil &rest &optional &key)))
228 (push (pop args) res))
229 (nreverse res)))
230
231;;;###autoload 234;;;###autoload
232(defun cl-generic-define (name args options) 235(defun cl-generic-define (name args options)
233 (let ((generic (cl-generic-ensure-function name)) 236 (pcase-let* ((generic (cl-generic-ensure-function name))
234 (mandatory (cl--generic-mandatory-args args)) 237 (`(,spec-args . ,_) (cl--generic-split-args args))
235 (apo (assq :argument-precedence-order options))) 238 (mandatory (mapcar #'car spec-args))
239 (apo (assq :argument-precedence-order options)))
236 (setf (cl--generic-dispatches generic) nil) 240 (setf (cl--generic-dispatches generic) nil)
237 (when apo 241 (when apo
238 (dolist (arg (cdr apo)) 242 (dolist (arg (cdr apo))
@@ -259,52 +263,70 @@ This macro can only be used within the lexical scope of a cl-generic method."
259 (and (memq sexp vars) (not (memq sexp res)) (push sexp res)) 263 (and (memq sexp vars) (not (memq sexp res)) (push sexp res))
260 res)) 264 res))
261 265
262 (defun cl--generic-lambda (args body) 266 (defun cl--generic-split-args (args)
263 "Make the lambda expression for a method with ARGS and BODY." 267 "Return (SPEC-ARGS . PLAIN-ARGS)."
264 (let ((plain-args ()) 268 (let ((plain-args ())
265 (specializers nil) 269 (specializers nil)
266 (mandatory t)) 270 (mandatory t))
267 (dolist (arg args) 271 (dolist (arg args)
268 (push (pcase arg 272 (push (pcase arg
269 ((or '&optional '&rest '&key) (setq mandatory nil) arg) 273 ((or '&optional '&rest '&key) (setq mandatory nil) arg)
270 ((and `(,name . ,type) (guard mandatory)) 274 ('&context
275 (unless mandatory
276 (error "&context not immediately after mandatory args"))
277 (setq mandatory 'context) nil)
278 ((let 'nil mandatory) arg)
279 ((let 'context mandatory)
280 (unless (consp arg)
281 (error "Invalid &context arg: %S" arg))
282 (push `((&context . ,(car arg)) . ,(cadr arg)) specializers)
283 nil)
284 (`(,name . ,type)
271 (push (cons name (car type)) specializers) 285 (push (cons name (car type)) specializers)
272 name) 286 name)
273 (_ arg)) 287 (_
288 (push (cons arg t) specializers)
289 arg))
274 plain-args)) 290 plain-args))
275 (setq plain-args (nreverse plain-args)) 291 (cons (nreverse specializers)
276 (let ((fun `(cl-function (lambda ,plain-args ,@body))) 292 (nreverse (delq nil plain-args)))))
277 (macroenv (cons `(cl-generic-current-method-specializers 293
278 . ,(lambda () specializers)) 294 (defun cl--generic-lambda (args body)
279 macroexpand-all-environment))) 295 "Make the lambda expression for a method with ARGS and BODY."
280 (require 'cl-lib) ;Needed to expand `cl-flet' and `cl-function'. 296 (pcase-let* ((`(,spec-args . ,plain-args)
281 ;; First macroexpand away the cl-function stuff (e.g. &key and 297 (cl--generic-split-args args))
282 ;; destructuring args, `declare' and whatnot). 298 (fun `(cl-function (lambda ,plain-args ,@body)))
283 (pcase (macroexpand fun macroenv) 299 (macroenv (cons `(cl-generic-current-method-specializers
284 (`#'(lambda ,args . ,body) 300 . ,(lambda () spec-args))
285 (let* ((parsed-body (macroexp-parse-body body)) 301 macroexpand-all-environment)))
286 (cnm (make-symbol "cl--cnm")) 302 (require 'cl-lib) ;Needed to expand `cl-flet' and `cl-function'.
287 (nmp (make-symbol "cl--nmp")) 303 ;; First macroexpand away the cl-function stuff (e.g. &key and
288 (nbody (macroexpand-all 304 ;; destructuring args, `declare' and whatnot).
289 `(cl-flet ((cl-call-next-method ,cnm) 305 (pcase (macroexpand fun macroenv)
290 (cl-next-method-p ,nmp)) 306 (`#'(lambda ,args . ,body)
291 ,@(cdr parsed-body)) 307 (let* ((parsed-body (macroexp-parse-body body))
292 macroenv)) 308 (cnm (make-symbol "cl--cnm"))
293 ;; FIXME: Rather than `grep' after the fact, the 309 (nmp (make-symbol "cl--nmp"))
294 ;; macroexpansion should directly set some flag when cnm 310 (nbody (macroexpand-all
295 ;; is used. 311 `(cl-flet ((cl-call-next-method ,cnm)
296 ;; FIXME: Also, optimize the case where call-next-method is 312 (cl-next-method-p ,nmp))
297 ;; only called with explicit arguments. 313 ,@(cdr parsed-body))
298 (uses-cnm (cl--generic-fgrep (list cnm nmp) nbody))) 314 macroenv))
299 (cons (not (not uses-cnm)) 315 ;; FIXME: Rather than `grep' after the fact, the
300 `#'(lambda (,@(if uses-cnm (list cnm)) ,@args) 316 ;; macroexpansion should directly set some flag when cnm
301 ,@(car parsed-body) 317 ;; is used.
302 ,(if (not (memq nmp uses-cnm)) 318 ;; FIXME: Also, optimize the case where call-next-method is
303 nbody 319 ;; only called with explicit arguments.
304 `(let ((,nmp (lambda () 320 (uses-cnm (cl--generic-fgrep (list cnm nmp) nbody)))
305 (cl--generic-isnot-nnm-p ,cnm)))) 321 (cons (not (not uses-cnm))
306 ,nbody)))))) 322 `#'(lambda (,@(if uses-cnm (list cnm)) ,@args)
307 (f (error "Unexpected macroexpansion result: %S" f))))))) 323 ,@(car parsed-body)
324 ,(if (not (memq nmp uses-cnm))
325 nbody
326 `(let ((,nmp (lambda ()
327 (cl--generic-isnot-nnm-p ,cnm))))
328 ,nbody))))))
329 (f (error "Unexpected macroexpansion result: %S" f))))))
308 330
309 331
310;;;###autoload 332;;;###autoload
@@ -375,21 +397,26 @@ which case this method will be invoked when the argument is `eql' to VAL.
375 397
376;;;###autoload 398;;;###autoload
377(defun cl-generic-define-method (name qualifiers args uses-cnm function) 399(defun cl-generic-define-method (name qualifiers args uses-cnm function)
378 (let* ((generic (cl-generic-ensure-function name)) 400 (pcase-let*
379 (mandatory (cl--generic-mandatory-args args)) 401 ((generic (cl-generic-ensure-function name))
380 (specializers 402 (`(,spec-args . ,_) (cl--generic-split-args args))
381 (mapcar (lambda (arg) (if (consp arg) (cadr arg) t)) mandatory)) 403 (specializers (mapcar (lambda (spec-arg)
382 (method (cl--generic-make-method 404 (if (eq '&context (car-safe (car spec-arg)))
383 specializers qualifiers uses-cnm function)) 405 spec-arg (cdr spec-arg)))
384 (mt (cl--generic-method-table generic)) 406 spec-args))
385 (me (cl--generic-member-method specializers qualifiers mt)) 407 (method (cl--generic-make-method
386 (dispatches (cl--generic-dispatches generic)) 408 specializers qualifiers uses-cnm function))
387 (i 0)) 409 (mt (cl--generic-method-table generic))
388 (dolist (specializer specializers) 410 (me (cl--generic-member-method specializers qualifiers mt))
389 (let* ((generalizers (cl-generic-generalizers specializer)) 411 (dispatches (cl--generic-dispatches generic))
390 (x (assq i dispatches))) 412 (i 0))
413 (dolist (spec-arg spec-args)
414 (let* ((key (if (eq '&context (car-safe (car spec-arg)))
415 (car spec-arg) i))
416 (generalizers (cl-generic-generalizers (cdr spec-arg)))
417 (x (assoc key dispatches)))
391 (unless x 418 (unless x
392 (setq x (cons i (cl-generic-generalizers t))) 419 (setq x (cons key (cl-generic-generalizers t)))
393 (setf (cl--generic-dispatches generic) 420 (setf (cl--generic-dispatches generic)
394 (setq dispatches (cons x dispatches)))) 421 (setq dispatches (cons x dispatches))))
395 (dolist (generalizer generalizers) 422 (dolist (generalizer generalizers)
@@ -427,6 +454,7 @@ which case this method will be invoked when the argument is `eql' to VAL.
427(defun cl--generic-get-dispatcher (dispatch) 454(defun cl--generic-get-dispatcher (dispatch)
428 (cl--generic-with-memoization 455 (cl--generic-with-memoization
429 (gethash dispatch cl--generic-dispatchers) 456 (gethash dispatch cl--generic-dispatchers)
457 ;; (message "cl--generic-get-dispatcher (%S)" dispatch)
430 (let* ((dispatch-arg (car dispatch)) 458 (let* ((dispatch-arg (car dispatch))
431 (generalizers (cdr dispatch)) 459 (generalizers (cdr dispatch))
432 (lexical-binding t) 460 (lexical-binding t)
@@ -437,13 +465,14 @@ which case this method will be invoked when the argument is `eql' to VAL.
437 'arg)) 465 'arg))
438 generalizers)) 466 generalizers))
439 (typescodes 467 (typescodes
440 (mapcar (lambda (generalizer) 468 (mapcar
441 `(funcall ',(cl--generic-generalizer-specializers-function 469 (lambda (generalizer)
442 generalizer) 470 `(funcall ',(cl--generic-generalizer-specializers-function
443 ,(funcall (cl--generic-generalizer-tagcode-function 471 generalizer)
444 generalizer) 472 ,(funcall (cl--generic-generalizer-tagcode-function
445 'arg))) 473 generalizer)
446 generalizers)) 474 'arg)))
475 generalizers))
447 (tag-exp 476 (tag-exp
448 ;; Minor optimization: since this tag-exp is 477 ;; Minor optimization: since this tag-exp is
449 ;; only used to lookup the method-cache, it 478 ;; only used to lookup the method-cache, it
@@ -452,23 +481,30 @@ which case this method will be invoked when the argument is `eql' to VAL.
452 `(or ,@(if (macroexp-const-p (car (last tagcodes))) 481 `(or ,@(if (macroexp-const-p (car (last tagcodes)))
453 (butlast tagcodes) 482 (butlast tagcodes)
454 tagcodes))) 483 tagcodes)))
455 (extraargs ())) 484 (fixedargs '(arg))
456 (dotimes (_ dispatch-arg) 485 (dispatch-idx dispatch-arg)
457 (push (make-symbol "arg") extraargs)) 486 (bindings nil))
487 (when (eq '&context (car-safe dispatch-arg))
488 (setq bindings `((arg ,(cdr dispatch-arg))))
489 (setq fixedargs nil)
490 (setq dispatch-idx 0))
491 (dotimes (i dispatch-idx)
492 (push (make-symbol (format "arg%d" (- dispatch-idx i 1))) fixedargs))
458 ;; FIXME: For generic functions with a single method (or with 2 methods, 493 ;; FIXME: For generic functions with a single method (or with 2 methods,
459 ;; one of which always matches), using a tagcode + hash-table is 494 ;; one of which always matches), using a tagcode + hash-table is
460 ;; overkill: better just use a `cl-typep' test. 495 ;; overkill: better just use a `cl-typep' test.
461 (byte-compile 496 (byte-compile
462 `(lambda (generic dispatches-left methods) 497 `(lambda (generic dispatches-left methods)
463 (let ((method-cache (make-hash-table :test #'eql))) 498 (let ((method-cache (make-hash-table :test #'eql)))
464 (lambda (,@extraargs arg &rest args) 499 (lambda (,@fixedargs &rest args)
465 (apply (cl--generic-with-memoization 500 (let ,bindings
466 (gethash ,tag-exp method-cache) 501 (apply (cl--generic-with-memoization
467 (cl--generic-cache-miss 502 (gethash ,tag-exp method-cache)
468 generic ',dispatch-arg dispatches-left methods 503 (cl--generic-cache-miss
469 ,(if (cdr typescodes) 504 generic ',dispatch-arg dispatches-left methods
470 `(append ,@typescodes) (car typescodes)))) 505 ,(if (cdr typescodes)
471 ,@extraargs arg args)))))))) 506 `(append ,@typescodes) (car typescodes))))
507 ,@fixedargs args)))))))))
472 508
473(defun cl--generic-make-function (generic) 509(defun cl--generic-make-function (generic)
474 (cl--generic-make-next-function generic 510 (cl--generic-make-next-function generic
@@ -593,8 +629,11 @@ FUN is the function that should be called when METHOD calls
593 dispatch-arg dispatches-left methods-left types) 629 dispatch-arg dispatches-left methods-left types)
594 (let ((methods '())) 630 (let ((methods '()))
595 (dolist (method methods-left) 631 (dolist (method methods-left)
596 (let* ((specializer (or (nth dispatch-arg 632 (let* ((specializer (or (if (integerp dispatch-arg)
597 (cl--generic-method-specializers method)) 633 (nth dispatch-arg
634 (cl--generic-method-specializers method))
635 (cdr (assoc dispatch-arg
636 (cl--generic-method-specializers method))))
598 t)) 637 t))
599 (m (member specializer types))) 638 (m (member specializer types)))
600 (when m 639 (when m
@@ -830,6 +869,17 @@ Can only be used from within the lexical body of a primary or around method."
830 80 (lambda (name) `(gethash (car-safe ,name) cl--generic-head-used)) 869 80 (lambda (name) `(gethash (car-safe ,name) cl--generic-head-used))
831 (lambda (tag) (if (eq (car-safe tag) 'head) (list tag))))) 870 (lambda (tag) (if (eq (car-safe tag) 'head) (list tag)))))
832 871
872;; Pre-fill the cl--generic-dispatchers table.
873;; We have two copies of `(0 ...)' but we can't share them via `let' because
874;; they're not used at the same time (one is compile-time, one is run-time).
875(puthash `(0 ,cl--generic-head-generalizer ,cl--generic-t-generalizer)
876 (eval-when-compile
877 (unless (fboundp 'cl--generic-get-dispatcher)
878 (require 'cl-generic))
879 (cl--generic-get-dispatcher
880 `(0 ,cl--generic-head-generalizer ,cl--generic-t-generalizer)))
881 cl--generic-dispatchers)
882
833(cl-defmethod cl-generic-generalizers :extra "head" (specializer) 883(cl-defmethod cl-generic-generalizers :extra "head" (specializer)
834 "Support for the `(head VAL)' specializers." 884 "Support for the `(head VAL)' specializers."
835 ;; We have to implement `head' here using the :extra qualifier, 885 ;; We have to implement `head' here using the :extra qualifier,
@@ -948,40 +998,6 @@ Can only be used from within the lexical body of a primary or around method."
948 (list cl--generic-typeof-generalizer))) 998 (list cl--generic-typeof-generalizer)))
949 (cl-call-next-method))) 999 (cl-call-next-method)))
950 1000
951;;; Just for kicks: dispatch on major-mode
952;;
953;; Here's how you'd use it:
954;; (cl-defmethod foo ((x (major-mode text-mode)) y z) ...)
955;; And then
956;; (foo 'major-mode toto titi)
957;;
958;; FIXME: Better would be to do that via dispatch on an "implicit argument".
959;; E.g. (cl-defmethod foo (y z &context (major-mode text-mode)) ...)
960
961;; (defvar cl--generic-major-modes (make-hash-table :test #'eq))
962;;
963;; (add-function :before-until cl-generic-generalizer-function
964;; #'cl--generic-major-mode-tagcode)
965;; (defun cl--generic-major-mode-tagcode (type name)
966;; (if (eq 'major-mode (car-safe type))
967;; `(50 . (if (eq ,name 'major-mode)
968;; (cl--generic-with-memoization
969;; (gethash major-mode cl--generic-major-modes)
970;; `(cl--generic-major-mode . ,major-mode))))))
971;;
972;; (add-function :before-until cl-generic-tag-types-function
973;; #'cl--generic-major-mode-types)
974;; (defun cl--generic-major-mode-types (tag)
975;; (when (eq (car-safe tag) 'cl--generic-major-mode)
976;; (if (eq tag 'fundamental-mode) '(fundamental-mode t)
977;; (let ((types `((major-mode ,(cdr tag)))))
978;; (while (get (car types) 'derived-mode-parent)
979;; (push (list 'major-mode (get (car types) 'derived-mode-parent))
980;; types))
981;; (unless (eq 'fundamental-mode (car types))
982;; (push '(major-mode fundamental-mode) types))
983;; (nreverse types)))))
984
985;; Local variables: 1001;; Local variables:
986;; generated-autoload-file: "cl-loaddefs.el" 1002;; generated-autoload-file: "cl-loaddefs.el"
987;; End: 1003;; End:
diff --git a/test/automated/cl-generic-tests.el b/test/automated/cl-generic-tests.el
index 5194802fa00..a6035d1cba2 100644
--- a/test/automated/cl-generic-tests.el
+++ b/test/automated/cl-generic-tests.el
@@ -179,5 +179,15 @@
179 (cl-list* "quatre" (cl-next-method-p) (cl-call-next-method))) 179 (cl-list* "quatre" (cl-next-method-p) (cl-call-next-method)))
180 (should (equal (cl--generic-1 4 5) '("quatre" t 4 5 nil)))) 180 (should (equal (cl--generic-1 4 5) '("quatre" t 4 5 nil))))
181 181
182(ert-deftest sm-generic-test-12-context ()
183 (cl-defgeneric cl--generic-1 ())
184 (cl-defmethod cl--generic-1 (&context (overwrite-mode (eql t))) 'is-t)
185 (cl-defmethod cl--generic-1 (&context (overwrite-mode (eql nil))) 'is-nil)
186 (cl-defmethod cl--generic-1 () 'other)
187 (should (equal (list (let ((overwrite-mode t)) (cl--generic-1))
188 (let ((overwrite-mode nil)) (cl--generic-1))
189 (let ((overwrite-mode 1)) (cl--generic-1)))
190 '(is-t is-nil other))))
191
182(provide 'cl-generic-tests) 192(provide 'cl-generic-tests)
183;;; cl-generic-tests.el ends here 193;;; cl-generic-tests.el ends here