reddit / emacs

reading a variable length argument using interactive

rese2020 · 2022-01-30 21:17
how to provide a read-string for every argument of a function that is being created using a macro for example: i have the macro provided, i would do this to create the function `(test-macro test-function (arg1 arg2 arg3 ))` the function `test-function` should be interactive and whenever called it should prompt me for arg1, arg2, and arg3 what i have tried: (defmacro test-macro (name args) `(progn (defun ,name ,args (interactive (list ,(let ((count 0) (lst ())) (while (< count (length args)) (push `(read-string ,(concat (symbol-name (nth count args)) ": ")) lst) (setq count (+ count 1))) lst))) ))) everything works fine but the part of `(read-string)` results in invalid-function error
7890yuiop · 2022-01-30 21:49
Macroexpand your example to see what's going wrong, and you'll see that: (test-macro test-function (arg1 arg2 arg3)) expands to: (progn (defun test-function (arg1 arg2 arg3) (interactive (list ((read-string "arg3: ") (read-string "arg2: ") (read-string "arg1: ")))))) Hence the invalid-function error. Change `,(let ((count 0) (lst ()))` to `,@(let ((count 0) (lst ()))` in order to splice that list and obtain the intended syntax.
agrostis · 2022-01-30 22:14
You can use ``macroexpand`` and ``macroexpand-1`` to debug the expansion of your macros. For instance, ``(macroexpand-1 '(test-macro foo (a b c)))`` evaluates to: (progn (defun foo (a b c) (interactive (list ((read-string "c: ") (read-string "b: ") (read-string "a: ")))))) This is incorrect syntax, because ``(read-string "c: ")`` occurs in the the head position of a combination, but it's neither a function designator, nor a symbol introducing a macro or special form. In other words, your three ``read-string`` are inside an extra list. The shortest way to fix this is by replacing ``,`` with ``,@`` in the 5th line of your macro definition. However, there's a further problem: the order of arguments in your read form sequence is opposite of what you need. The reason for this is that Lisp ``push`` adds elements to the head of a list, rather than to its tail (as you might be used to from languages such as Python). Here's a more idiomatic implementation, using the ``loop`` construction to iterate over your arguments: (defmacro test-macro-loop (name args) `(defun ,name ,args (interactive (list ,@(loop for arg in args collect `(read-string ,(format "%s: " arg))) )) )) Another version, with the more functional ``mapcar`` instead of ``loop``: (defmacro test-macro-mapcar (name args) `(defun ,name ,args (interactive (list ,@(mapcar (lambda (arg) `(read-string ,(format "%s: " arg))) args))) )) And this one constructs a multi-line ``interactive`` spec using ``mapconcat``: (defmacro test-macro-mapconcat (name args) `(defun ,name ,args (interactive ,(mapconcat (lambda (arg) (format "s%s: " arg)) args "\n")) ))