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")) ))