Subject: [PATCH] Add a version of cl-once-only which handles lists of forms
From: Thuna <[email-masked]>
To: [email-masked]
Date: Sun, 28 Jul 2024 23:17:31 +0200
List-Id: <debbugs-submit.debbugs.gnu.org>

Attached is the macro `cl-once-only*', which takes a symbol which is
bound to a list of form (such as those contained in a `&rest args' in
`defmacro') and binds that variable to a list of symbols where each
symbol is bound to the result of evaluating (in order) the corresponding
form.  The docstring provides an example which might illustrate what it
does better then I did here.

I don't expect this to be used too often but it could be somewhat handy
in macros with a `&rest args' which are supposed to be evaluated.

(My motivation for this is to use this macro in another patch which I
didn't want to make longer with out-of-scope additions.  If this is
considered not worth polluting the `cl-' namespace over I can just write
the forms by hand so it's fine either way.)

From e8af4374454965d541ad3b8062e6793217d4e86f Mon Sep 17 00:00:00 2001
From: Thuna <[email-masked]>
Date: Sun, 28 Jul 2024 22:26:41 +0200
Subject: [PATCH] Add a version of cl-once-only which handles lists of forms

* lisp/emacs-lisp/cl-macs.el (cl-once-only*): Define macro.
---
 lisp/emacs-lisp/cl-macs.el | 37 +++++++++++++++++++++++++++++++++++++
 1 file changed, 37 insertions(+)

diff --git a/lisp/emacs-lisp/cl-macs.el b/lisp/emacs-lisp/cl-macs.el
index 2e501005bf7..70d66b3a250 100644
--- a/lisp/emacs-lisp/cl-macs.el
+++ b/lisp/emacs-lisp/cl-macs.el
@@ -2544,6 +2544,43 @@ cl-once-only
                           collect `(,(car name) ,gensym))
              ,@body)))))
 
+(defmacro cl-once-only* (listvar &rest body)
+  "Generate code to evaluate the list of forms LISTVAR just once in BODY.
+
+This is a macro to be used while defining other macros.  LISTVAR should
+be a variable which holds a list of forms, such as ARGS in a function
+call of the form \\=`(,head ,@args).  Within BODY, the variable LISTVAR
+will be bound to a list of uninterned symbols of the same length, all of
+which are variables in the final macroexpansion which are bound to the
+result of evaluating the corresponding form.
+
+For example, the following macro
+
+  (defmacro my-list (head &rest args)
+    (cl-once-only* args
+      \\=`(list (,head ,@args) ,@args)))
+
+when called like
+
+  (let ((x \\='(1 5 4)))
+    (my-list + (pop x) (1+ (pop x)) (1- (pop x))))
+
+will expand into
+
+  (let ((x \\='(1 5 4)))
+    (let* ((arg1 (pop x)) (arg2 (1+ (pop x))) (arg3 (1- (pop x))))
+      (list (+ arg1 arg2 arg3) arg1 arg2 arg3)))
+
+and the arguments will be evaluated only once and in order."
+  (declare (debug (arg body)) (indent 1))
+  (let ((args (gensym (symbol-name listvar))))
+    `(let ((,args
+            (cl-loop for i from 1 to (length ,listvar) collect
+                     (make-symbol (format "%s$%d" ',(symbol-name args) i)))))
+       `(let* ,(cl-mapcar #'list ,args ,listvar)
+          ,(let ((,listvar ,args))
+             ,@body)))))
+
 ;;; Multiple values.
 
 ;;;###autoload
-- 
2.44.2