emacs-elpa-diffs
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[elpa] externals/compat d5df5e2f5e: compat-29: Add cl-with-gensyms and c


From: ELPA Syncer
Subject: [elpa] externals/compat d5df5e2f5e: compat-29: Add cl-with-gensyms and cl-once-only
Date: Sun, 5 Feb 2023 18:57:23 -0500 (EST)

branch: externals/compat
commit d5df5e2f5e204ac2f1f31a681b7c0e4f9f7f425b
Author: Daniel Mendler <mail@daniel-mendler.de>
Commit: Daniel Mendler <mail@daniel-mendler.de>

    compat-29: Add cl-with-gensyms and cl-once-only
---
 NEWS.org        |  4 +++
 compat-29.el    | 47 +++++++++++++++++++++++++++++++++++
 compat-tests.el | 16 ++++++++++++
 compat.texi     | 77 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 144 insertions(+)

diff --git a/NEWS.org b/NEWS.org
index eaaf8031fa..9d6a4e55e8 100644
--- a/NEWS.org
+++ b/NEWS.org
@@ -1,5 +1,9 @@
 #+title: compat.el - Changelog
 
+* Development
+
+- compat-29: Add ~cl-with-gensyms~ and ~cl-once-only~.
+
 * Release of "Compat" Version 29.1.3.2
 
 - compat-26: Add ~make-temp-file~ with optional argument TEXT.
diff --git a/compat-29.el b/compat-29.el
index bc508fb37d..15a8415782 100644
--- a/compat-29.el
+++ b/compat-29.el
@@ -1340,6 +1340,53 @@ Also see `buttonize'."
           (setq sentences (1- sentences)))
         sentences))))
 
+;;;; Defined in cl-macs.el
+
+(compat-defmacro cl-with-gensyms (names &rest body) ;; 
<compat-tests:cl-with-gensyms>
+  "Bind each of NAMES to an uninterned symbol and evaluate BODY."
+  ;; No :feature since macro is autoloaded
+  (declare (debug (sexp body)) (indent 1))
+  `(let ,(cl-loop for name in names collect
+                  `(,name (gensym (symbol-name ',name))))
+     ,@body))
+
+(compat-defmacro cl-once-only (names &rest body) ;; <compat-tests:cl-once-only>
+  "Generate code to evaluate each of NAMES just once in BODY.
+
+This macro helps with writing other macros.  Each of names is
+either (NAME FORM) or NAME, which latter means (NAME NAME).
+During macroexpansion, each NAME is bound to an uninterned
+symbol.  The expansion evaluates each FORM and binds it to the
+corresponding uninterned symbol.
+
+For example, consider this macro:
+
+    (defmacro my-cons (x)
+      (cl-once-only (x)
+        \\=`(cons ,x ,x)))
+
+The call (my-cons (pop y)) will expand to something like this:
+
+    (let ((g1 (pop y)))
+      (cons g1 g1))
+
+The use of `cl-once-only' ensures that the pop is performed only
+once, as intended.
+
+See also `macroexp-let2'."
+  ;; No :feature since macro is autoloaded
+  (declare (debug (sexp body)) (indent 1))
+  (setq names (mapcar #'ensure-list names))
+  (let ((our-gensyms (cl-loop for _ in names collect (gensym))))
+    `(let ,(cl-loop for sym in our-gensyms collect `(,sym (gensym)))
+       `(let ,(list
+               ,@(cl-loop for name in names for gensym in our-gensyms
+                          for to-eval = (or (cadr name) (car name))
+                          collect ``(,,gensym ,,to-eval)))
+          ,(let ,(cl-loop for name in names for gensym in our-gensyms
+                          collect `(,(car name) ,gensym))
+             ,@body)))))
+
 ;;;; Defined in ert-x.el
 
 (compat-defmacro ert-with-temp-file (name &rest body) ;; 
<compat-tests:ert-with-temp-file>
diff --git a/compat-tests.el b/compat-tests.el
index 7cd67d0a33..2b5f1bb8a3 100644
--- a/compat-tests.el
+++ b/compat-tests.el
@@ -2899,5 +2899,21 @@
     (should (directory-name-p dir))
     (should (file-directory-p dir))))
 
+(defmacro compat-tests--with-gensyms ()
+  (cl-with-gensyms (x y)
+    `(let ((,x 1) (,y 2)) (+ ,x ,y))))
+
+(ert-deftest cl-with-gensyms ()
+  (should-equal 3 (compat-tests--with-gensyms)))
+
+(defmacro compat-tests--once-only (x)
+  (cl-once-only (x)
+    `(cons ,x ,x)))
+
+(ert-deftest cl-once-only ()
+  (let ((x 0))
+    (should-equal (cons 1 1) (compat-tests--once-only (cl-incf x)))
+    (should-equal 1 x)))
+
 (provide 'compat-tests)
 ;;; compat-tests.el ends here
diff --git a/compat.texi b/compat.texi
index 7c18051fbc..0b251f271f 100644
--- a/compat.texi
+++ b/compat.texi
@@ -2940,6 +2940,81 @@ The same keyword arguments are supported as in
 @code{ert-with-temp-file} (which see), except for @code{:text}.
 @end defmac
 
+@c copied from lispref/cl.texi
+@defmac cl-with-gensyms names@dots{} body
+This macro expands to code that executes @var{body} with each of the
+variables in @var{names} bound to a fresh uninterned symbol, or
+@dfn{gensym}, in Common Lisp parlance.  For macros requiring more than
+one gensym, use of @code{cl-with-gensyms} shortens the code and
+renders one's intentions clearer.  Compare:
+
+@example
+(defmacro my-macro (foo)
+  (let ((bar (gensym "bar"))
+        (baz (gensym "baz"))
+        (quux (gensym "quux")))
+    `(let ((,bar (+ @dots{})))
+       @dots{})))
+
+(defmacro my-macro (foo)
+  (cl-with-gensyms (bar baz quux)
+    `(let ((,bar (+ @dots{})))
+       @dots{})))
+@end example
+@end defmac
+
+@c copied from lispref/cl.texi
+@defmac cl-once-only ((variable form)@dots{}) body
+This macro is primarily to help the macro programmer ensure that forms
+supplied by the user of the macro are evaluated just once by its
+expansion even though the result of evaluating the form is to occur
+more than once.  Less often, this macro is used to ensure that forms
+supplied by the macro programmer are evaluated just once.
+
+Each @var{variable} may be used to refer to the result of evaluating
+@var{form} in @var{body}.  @code{cl-once-only} binds each
+@var{variable} to a fresh uninterned symbol during the evaluation of
+@var{body}.  Then, @code{cl-once-only} wraps the final expansion in
+code to evaluate each @var{form} and bind the result to the
+corresponding uninterned symbol.  Thus, when the macro writer
+substitutes the value for @var{variable} into the expansion they are
+effectively referring to the result of evaluating @var{form}, rather
+than @var{form} itself.  Another way to put this is that each
+@var{variable} is bound to an expression for the (singular) result of
+evaluating @var{form}.
+
+The most common case is where @var{variable} is one of the arguments
+to the macro being written, so @code{(variable variable)} may be
+abbreviated to just @code{variable}.
+
+For example, consider this macro:
+
+@example
+(defmacro my-list (x y &rest forms)
+  (let ((x-result (gensym))
+        (y-result (gensym)))
+    `(let ((,x-result ,x)
+           (,y-result ,y))
+       (list ,x-result ,y-result ,x-result ,y-result
+             (progn ,@@forms))))
+@end example
+
+In a call like @w{@code{(my-list (pop foo) @dots{})}} the intermediate
+binding to @code{x-result} ensures that the @code{pop} is not done
+twice.  But as a result the code is rather complex: the reader must
+keep track of how @code{x-result} really just means the first
+parameter of the call to the macro, and the required use of multiple
+gensyms to avoid variable capture by @code{(progn ,@@forms)} obscures
+things further.  @code{cl-once-only} takes care of these details:
+
+@example
+(defmacro my-list (x y &rest forms)
+  (cl-once-only (x y)
+    `(list ,x ,y ,x ,y
+           (progn ,@@forms))))
+@end example
+@end defmac
+
 @subsection Extended Definitions
 These functions must be called explicitly via @code{compat-call},
 since their calling convention or behavior was extended in Emacs 29.1:
@@ -3088,6 +3163,8 @@ The function @code{textsec-suspicious-p}.
 The function @code{minibuffer-lazy-highlight-setup}.
 @item
 The function @code{pp-emacs-lisp-code}.
+@item
+The library oclosure.el (Open Closures).
 @end itemize
 
 @node Development



reply via email to

[Prev in Thread] Current Thread [Next in Thread]