diff lisp/specifier.el @ 290:c9fe270a4101 r21-0b43

Import from CVS: tag r21-0b43
author cvs
date Mon, 13 Aug 2007 10:36:47 +0200
parents e11d67e05968
children 70ad99077275
line wrap: on
line diff
--- a/lisp/specifier.el	Mon Aug 13 10:35:55 2007 +0200
+++ b/lisp/specifier.el	Mon Aug 13 10:36:47 2007 +0200
@@ -403,88 +403,97 @@
 	    how-to-add))))
   value)
 
-;; Note: you cannot replace the following macro with `letf' because
-;; `specifier-instance' does not have a setf method defined.  (I tried
-;; to use `set-specifier' as the setf method for `specifier-instance',
-;; but it doesn't work for `letf' because set-specifier to the old
-;; value cannot be used to "undo" a previous set-specifier, as letf
-;; expects.)
-;;
-;; This macro might perhaps be made simpler, with an addition to
-;; `remove-specifier'.  Example (simplified for clarity):
-;;
-;; (defmacro let-specifier (specifier value domain &rest body)
-;;   `(unwind-protect
-;;        (progn
-;;	    (add-spec-to-specifier ,specifier ,value ,domain nil 'prepend)
-;;	    ,@body)
-;;      (remove-specifier ,specifier ,domain)))
-;;
-;; So, the idea is to unconditionally prepend a specification for
-;; DOMAIN, and unconditionally remove it.  This does not work because
-;; `remove-specifier' removes *all* the specifications of DOMAIN,
-;; nuking the old ones, in the process.  (for this purpose, it might
-;; make sense for `remove-specifier' to have a HOW-TO-REMOVE
-;; argument.)
-;;
-;; The following version remembers the old speclist and returns it
-;; later.  It's probably less error-prone anyway.
+(defmacro let-specifier (specifier-list &rest body)
+  "Add specifier specs, evaluate forms in BODY and restore the specifiers.
+\(let-specifier SPECIFIER-LIST BODY...)
+
+Each element of SPECIFIER-LIST should look like this:
+\(SPECIFIER VALUE &optional LOCALE TAG-SET HOW-TO-ADD).
 
-(defmacro let-specifier (specifier-list &rest body)
-  "(let-specifier SPECIFIER-LIST BODY): bind specifiers and evaluate BODY.
+SPECIFIER is the specifier to be temporarily modified.  VALUE is the
+instantiator to be temporarily added to SPECIFIER in LOCALE.  LOCALE,
+TAG-SET and HOW-TO-ADD have the same meaning as in
+`add-spec-to-specifier'.
+
+The code resulting from macro expansion will add specifications to
+specifiers using `add-spec-to-specifier'.  After BODY is finished, the
+temporary specifications are removed and old spec-lists are restored.
+
+LOCALE, TAG-SET and HOW-TO-ADD may be omitted, and default to nil.
 The value of the last form in BODY is returned.
-Each element of SPECIFIER-LIST should be a list of
-\(SPECIFIER VALUE DOMAIN).  VALUE and DOMAIN may be omitted, and default
-to nil.  The elements of SPECIFIER-LIST are evaluated sequentially.
+
+NOTE: If you want the specifier's instance to change in all
+circumstances, use (selected-window) as the LOCALE.  If LOCALE is nil
+or omitted, it defaults to `global'.
 
-For meaning of DOMAIN, see `specifier-instance'."
-  ;; Error-checking
-  (dolist (listel specifier-list)
-    (or (and (consp listel)
-	     (<= (length listel) 3))
-	(signal 'error (list "Should be a 3-element list" listel))))
-  ;; Set up fresh symbols to avoid name clashes.
-  (let* ((specvarlist (mapcar #'(lambda (ignored) (gensym "specifier-"))
-			      specifier-list))
-	 (valvarlist (mapcar #'(lambda (ignored) (gensym "value-"))
-			     specifier-list))
-	 (domvarlist (mapcar #'(lambda (ignored) (gensym "domain-"))
-			     specifier-list))
-	 (oldvarlist (mapcar #'(lambda (ignored) (gensym "old-"))
-			     specifier-list)))
-    ;; Bind the appropriate variables.
-    `(let* (,@(mapcar* (lambda (symbol listel)
-			 (list symbol (nth 0 listel)))
-		       specvarlist specifier-list)
-	      ,@(mapcar* (lambda (symbol listel)
-			   (list symbol (nth 1 listel)))
-			 valvarlist specifier-list)
-	      ,@(mapcar* (lambda (symbol listel)
-			   (list symbol (nth 2 listel)))
-			 domvarlist specifier-list)
-	      ,@(mapcar* (lambda (symbol specifier domain)
-			   (list symbol `(specifier-spec-list
-					  ,specifier ,domain)))
-			 oldvarlist specvarlist domvarlist))
-       (unwind-protect
-	   (progn
-	     ,@(mapcar* (lambda (specifier value domain)
-			  `(add-spec-to-specifier
-			    ,specifier ,value ,domain
-			    nil 'prepend))
-			specvarlist valvarlist domvarlist)
-	     ,@body)
-	 ,@(apply
-	    #'nconc
-	    ;; Reverse the unwinding order for marginal safety gain.
-	    (nreverse
-	     (mapcar*
-	      (lambda (specifier domain oldvalue)
-		`((remove-specifier ,specifier ,domain)
-		  (add-spec-list-to-specifier ,specifier ,oldvalue)))
-	      specvarlist domvarlist oldvarlist)))))))
+Example:
+    (let-specifier ((modeline-shadow-thickness 0 (selected-window)))
+      (sit-for 1))"
+  (check-argument-type 'listp specifier-list)
+  (flet ((gensym-frob (x name)
+	   (if (or (atom x) (eq (car x) 'quote))
+	       (list x)
+	     (list (gensym name) x))))
+    ;; VARLIST is a list of
+    ;; ((SPECIFIERSYM SPECIFIER) (VALUE) (LOCALESYM LOCALE)
+    ;;  (TAG-SET) (HOW-TO-ADD))
+    ;; If any of these is an atom, then a separate symbol is
+    ;; unnecessary, the CAR will contain the atom and CDR will be nil.
+    (let* ((varlist (mapcar #'(lambda (listel)
+				(or (and (consp listel)
+					 (<= (length listel) 5)
+					 (> (length listel) 1))
+				    (signal 'error
+					    (list
+					     "should be a list of 2-5 elements"
+					     listel)))
+				;; VALUE, TAG-SET and HOW-TO-ADD are
+				;; referenced only once, so we needn't
+				;; frob them with gensym.
+				(list (gensym-frob (nth 0 listel) "specifier-")
+				      (list (nth 1 listel))
+				      (gensym-frob (nth 2 listel) "locale-")
+				      (list (nth 3 listel))
+				      (list (nth 4 listel))))
+			    specifier-list))
+	   ;; OLDVALLIST is a list of (OLDVALSYM OLDVALFORM)
+	   (oldvallist (mapcar #'(lambda (varel)
+				   (list (gensym "old-")
+					 `(specifier-spec-list
+					   ,(car (nth 0 varel))
+					   ,(car (nth 2 varel)))))
+			       varlist)))
+      ;; Bind the appropriate variables.
+      `(let* (,@(mapcan #'(lambda (varel)
+			    (delq nil (mapcar
+				       #'(lambda (varcons)
+					   (and (cdr varcons) varcons))
+				       varel)))
+			varlist)
+		,@oldvallist)
+	 (unwind-protect
+	     (progn
+	       ,@(mapcar #'(lambda (varel)
+			     `(add-spec-to-specifier
+			       ,(car (nth 0 varel)) ,(car (nth 1 varel))
+			       ,(car (nth 2 varel)) ,(car (nth 3 varel))
+			       ,(car (nth 4 varel))))
+			 varlist)
+	       ,@body)
+	   ;; Reverse the unwinding order, so that using the same
+	   ;; specifier multiple times works.
+	   ,@(apply #'nconc (nreverse (mapcar*
+				       #'(lambda (oldval varel)
+					   `((remove-specifier
+					      ,(car (nth 0 varel))
+					      ,(car (nth 2 varel)))
+					     (add-spec-list-to-specifier
+					      ,(car (nth 0 varel))
+					      ,(car oldval))))
+				       oldvallist varlist))))))))
 
-;; (cl-prettyexpand '(let-specifier ((modeline-shadow-thickness 0 (selected-window)) (fubar 0 baz)) (sit-for 1)))
+;; Evaluate this for testing:
+; (cl-prettyexpand '(let-specifier ((modeline-shadow-thickness 0 (selected-window) 'x) (fubar (value) baz)) (sit-for 1)))
 
 (define-specifier-tag 'win 'device-on-window-system-p)