]> code.delx.au - gnu-emacs/blobdiff - lisp/emacs-lisp/gv.el
Merge branch 'emacs-25-merge'
[gnu-emacs] / lisp / emacs-lisp / gv.el
index bc87f131164cb3e82575d00ec43f67ea943cd18e..1fea38c49c1b544f5216204de55ad014130bf4fd 100644 (file)
@@ -74,6 +74,8 @@
 ;; (defvar gv--macro-environment nil
 ;;   "Macro expanders for generalized variables.")
 
+(define-error 'gv-invalid-place "%S is not a valid place expression")
+
 ;;;###autoload
 (defun gv-get (place do)
   "Build the code that applies DO to PLACE.
@@ -84,15 +86,17 @@ and SETTER is a function which returns the code to set PLACE when called
 with a (not necessarily copyable) Elisp expression that returns the value to
 set it to.
 DO must return an Elisp expression."
-  (if (symbolp place)
-      (funcall do place (lambda (v) `(setq ,place ,v)))
+  (cond
+   ((symbolp place) (funcall do place (lambda (v) `(setq ,place ,v))))
+   ((not (consp place)) (signal 'gv-invalid-place (list place)))
+   (t
     (let* ((head (car place))
            (gf (function-get head 'gv-expander 'autoload)))
       (if gf (apply gf do (cdr place))
-        (let ((me (macroexpand place    ;FIXME: expand one step at a time!
-                               ;; (append macroexpand-all-environment
-                               ;;         gv--macro-environment)
-                               macroexpand-all-environment)))
+        (let ((me (macroexpand-1 place
+                                 ;; (append macroexpand-all-environment
+                                 ;;         gv--macro-environment)
+                                 macroexpand-all-environment)))
           (if (and (eq me place) (get head 'compiler-macro))
               ;; Expand compiler macros: this takes care of all the accessors
               ;; defined via cl-defsubst, such as cXXXr and defstruct slots.
@@ -104,8 +108,19 @@ DO must return an Elisp expression."
           (if (eq me place)
               (if (and (symbolp head) (get head 'setf-method))
                   (error "Incompatible place needs recompilation: %S" head)
-                (error "%S is not a valid place expression" place))
-            (gv-get me do)))))))
+                (let* ((setter (gv-setter head)))
+                  (gv--defsetter head (lambda (&rest args) `(,setter ,@args))
+                                 do (cdr place))))
+            (gv-get me do))))))))
+
+(defun gv-setter (name)
+  ;; The name taken from Scheme's SRFI-17.  Actually, for SRFI-17, the argument
+  ;; could/should be a function value rather than a symbol.
+  "Return the symbol where the (setf NAME) function should be placed."
+  (if (get name 'gv-expander)
+      (error "gv-expander conflicts with (setf %S)" name))
+  ;; FIXME: This is wrong if `name' is uninterned (or interned elsewhere).
+  (intern (format "(setf %s)" name)))
 
 ;;;###autoload
 (defmacro gv-letplace (vars place &rest body)
@@ -158,8 +173,10 @@ arguments as NAME.  DO is a function as defined in `gv-get'."
 
 ;;;###autoload
 (or (assq 'gv-expander defun-declarations-alist)
-    (push `(gv-expander ,(apply-partially #'gv--defun-declaration 'gv-expander))
-         defun-declarations-alist))
+    (let ((x `(gv-expander
+               ,(apply-partially #'gv--defun-declaration 'gv-expander))))
+      (push x macro-declarations-alist)
+      (push x defun-declarations-alist)))
 ;;;###autoload
 (or (assq 'gv-setter defun-declarations-alist)
     (push `(gv-setter ,(apply-partially #'gv--defun-declaration 'gv-setter))
@@ -201,7 +218,7 @@ return a Lisp form that does the assignment.
 The first arg in ARGLIST (the one that receives VAL) receives an expression
 which can do arbitrary things, whereas the other arguments are all guaranteed
 to be pure and copyable.  Example use:
-  (gv-define-setter aref (v a i) `(aset ,a ,i ,v))"
+  (gv-define-setter aref (v a i) \\=`(aset ,a ,i ,v))"
   (declare (indent 2) (debug (&define name sexp body)))
   `(gv-define-expander ,name
      (lambda (do &rest args)
@@ -216,7 +233,7 @@ turned into calls of the form (SETTER ARGS... VAL).
 
 If FIX-RETURN is non-nil, then SETTER is not assumed to return VAL and
 instead the assignment is turned into something equivalent to
-  \(let ((temp VAL))
+  (let ((temp VAL))
     (SETTER ARGS... temp)
     temp)
 so as to preserve the semantics of `setf'."
@@ -243,6 +260,8 @@ The return value is the last VAL in the list.
 
 \(fn PLACE VAL PLACE VAL ...)"
   (declare (debug (&rest [gv-place form])))
+  (if (/= (logand (length args) 1) 0)
+      (signal 'wrong-number-of-arguments (list 'setf (length args))))
   (if (and args (null (cddr args)))
       (let ((place (pop args))
             (val (car args)))
@@ -282,9 +301,9 @@ The return value is the last VAL in the list.
 ;; containing a non-trivial `push' even before gv.el was loaded.
 ;;;###autoload
 (put 'gv-place 'edebug-form-spec 'edebug-match-form)
+
 ;; CL did the equivalent of:
 ;;(gv-define-macroexpand edebug-after (lambda (before index place) place))
-
 (put 'edebug-after 'gv-expander
      (lambda (do before index place)
        (gv-letplace (getter setter) place
@@ -357,6 +376,34 @@ The return value is the last VAL in the list.
   (macroexp-let2 nil v val
     `(with-current-buffer ,buf (set (make-local-variable ,var) ,v))))
 
+(gv-define-expander alist-get
+  (lambda (do key alist &optional default remove)
+    (macroexp-let2 macroexp-copyable-p k key
+      (gv-letplace (getter setter) alist
+        (macroexp-let2 nil p `(assq ,k ,getter)
+          (funcall do (if (null default) `(cdr ,p)
+                        `(if ,p (cdr ,p) ,default))
+                   (lambda (v)
+                     (macroexp-let2 nil v v
+                       (let ((set-exp
+                              `(if ,p (setcdr ,p ,v)
+                                 ,(funcall setter
+                                           `(cons (setq ,p (cons ,k ,v))
+                                                  ,getter)))))
+                         (cond
+                          ((null remove) set-exp)
+                          ((or (eql v default)
+                               (and (eq (car-safe v) 'quote)
+                                    (eq (car-safe default) 'quote)
+                                    (eql (cadr v) (cadr default))))
+                           `(if ,p ,(funcall setter `(delq ,p ,getter))))
+                          (t
+                           `(cond
+                             ((not (eql ,default ,v)) ,set-exp)
+                             (,p ,(funcall setter
+                                           `(delq ,p ,getter)))))))))))))))
+
+
 ;;; Some occasionally handy extensions.
 
 ;; While several of the "places" below are not terribly useful for direct use,
@@ -432,6 +479,32 @@ The return value is the last VAL in the list.
              (funcall do `(funcall (car ,gv))
                       (lambda (v) `(funcall (cdr ,gv) ,v))))))))
 
+(defmacro gv-synthetic-place (getter setter)
+  "Special place described by its setter and getter.
+GETTER and SETTER (typically obtained via `gv-letplace') get and
+set that place.  I.e. This macro allows you to do the \"reverse\" of what
+`gv-letplace' does.
+This macro only makes sense when used in a place."
+  (declare (gv-expander funcall))
+  (ignore setter)
+  getter)
+
+(defmacro gv-delay-error (place)
+  "Special place which delays the `gv-invalid-place' error to run-time.
+It behaves just like PLACE except that in case PLACE is not a valid place,
+the `gv-invalid-place' error will only be signaled at run-time when (and if)
+we try to use the setter.
+This macro only makes sense when used in a place."
+  (declare
+   (gv-expander
+    (lambda (do)
+      (condition-case err
+          (gv-get place do)
+        (gv-invalid-place
+         ;; Delay the error until we try to use the setter.
+         (funcall do place (lambda (_) `(signal ',(car err) ',(cdr err)))))))))
+  place)
+
 ;;; Even more debatable extensions.
 
 (put 'cons 'gv-expander
@@ -463,11 +536,22 @@ The return value is the last VAL in the list.
   "Return a reference to PLACE.
 This is like the `&' operator of the C language.
 Note: this only works reliably with lexical binding mode, except for very
-simple PLACEs such as (function-symbol 'foo) which will also work in dynamic
+simple PLACEs such as (function-symbol \\='foo) which will also work in dynamic
 binding mode."
-  (gv-letplace (getter setter) place
-    `(cons (lambda () ,getter)
-           (lambda (gv--val) ,(funcall setter 'gv--val)))))
+  (let ((code
+         (gv-letplace (getter setter) place
+           `(cons (lambda () ,getter)
+                  (lambda (gv--val) ,(funcall setter 'gv--val))))))
+    (if (or lexical-binding
+            ;; If `code' still starts with `cons' then presumably gv-letplace
+            ;; did not add any new let-bindings, so the `lambda's don't capture
+            ;; any new variables.  As a consequence, the code probably works in
+            ;; dynamic binding mode as well.
+            (eq (car-safe code) 'cons))
+        code
+      (macroexp--warn-and-return
+       "Use of gv-ref probably requires lexical-binding"
+       code))))
 
 (defsubst gv-deref (ref)
   "Dereference REF, returning the referenced value.
@@ -479,22 +563,13 @@ REF must have been previously obtained with `gv-ref'."
 ;;  … => (load "gv.el") => (macroexpand-all (defsubst gv-deref …)) => (macroexpand (defun …)) => (load "gv.el")
 (gv-define-setter gv-deref (v ref) `(funcall (cdr ,ref) ,v))
 
-;;; Vaguely related definitions that should be moved elsewhere.
-
-;; (defun alist-get (key alist)
-;;   "Get the value associated to KEY in ALIST."
-;;   (declare
-;;    (gv-expander
-;;     (lambda (do)
-;;       (macroexp-let2 macroexp-copyable-p k key
-;;         (gv-letplace (getter setter) alist
-;;           (macroexp-let2 nil p `(assoc ,k ,getter)
-;;             (funcall do `(cdr ,p)
-;;                      (lambda (v)
-;;                        `(if ,p (setcdr ,p ,v)
-;;                           ,(funcall setter
-;;                                     `(cons (cons ,k ,v) ,getter)))))))))))
-;;   (cdr (assoc key alist)))
+;; (defmacro gv-letref (vars place &rest body)
+;;   (declare (indent 2) (debug (sexp form &rest body)))
+;;   (require 'cl-lib) ;Can't require cl-lib at top-level for bootstrap reasons!
+;;   (gv-letplace (getter setter) place
+;;     `(cl-macrolet ((,(nth 0 vars) () ',getter)
+;;                    (,(nth 1 vars) (v) (funcall ',setter v)))
+;;        ,@body)))
 
 (provide 'gv)
 ;;; gv.el ends here