diff options
| author | Daniel Mendler <mail@daniel-mendler.de> | 2023-02-06 00:03:11 +0100 |
|---|---|---|
| committer | Daniel Mendler <mail@daniel-mendler.de> | 2023-02-06 00:04:45 +0100 |
| commit | d5df5e2f5e204ac2f1f31a681b7c0e4f9f7f425b (patch) | |
| tree | 506f66efc1b747d6e09196dcd6cc753a5784474a | |
| parent | f3c4dbd7da3f14d104e94fdb7dd0291fc6456536 (diff) | |
compat-29: Add cl-with-gensyms and cl-once-only
| -rw-r--r-- | NEWS.org | 4 | ||||
| -rw-r--r-- | compat-29.el | 47 | ||||
| -rw-r--r-- | compat-tests.el | 16 | ||||
| -rw-r--r-- | compat.texi | 77 |
4 files changed, 144 insertions, 0 deletions
@@ -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 bc508fb..15a8415 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 7cd67d0..2b5f1bb 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 7c18051..0b251f2 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 |
