2

I want to make a macro that takes a form and returns a string with some parts of the form evaluated. The form can have any number of elements (e.g. additional options for the title font, color, size,...). Say the macro is called "gset".

So this:

(gset title (format "\"x=%s\"" (+ 1 1))))

would expand to: "set title \"x=2\""

or

(let ((x 3)) (s (gset title (format "\"x=%s\"" x))))

would expand to "set title\"x=3\"". The form(s) that get expanded will not always be in the third position.

Edit:

The place this has a role is in a DSL for gnuplot in elisp I am working on. Here is an example.

(let* ((x '(0 1 2 3))
       (y (loop for xx in x collect (* xx xx))))
  (gnuplot
   (gset terminal png)
   (gset output "test.png")
   (gset title "A Title")
   (gset xlabel "x")
   (gset ylabel "y")
   (plot x y with lines title "data"))
  (format "[[./test.png]]"))

The gset macro is the one I was describing above. the plot macro has the x and y arguments in the definition, so they expand fine (although nothing after them would). This block above as is works fine and generates the figure. But if I add something like

(gset title (format "A Title (%s) datapoints" (length x)))

This doesn't work because the format form doesn't expand. It won't always be the third position where something like this might happen.

It seems like this should work because when I look at it I can see what I want to happen, which is if it is a symbol use the symbol name, and if it is a list, evaluate it and use that value.

SOLUTION:

I came up with this macro:

(defmacro vset (&rest args)
  `(format "set %s" (mapconcat
         'identity
         ',(mapcar (lambda (s)
                 (format "%s"
                     (if (listp s)
                     (eval s)
                       s)))
               args)
         " ")))

This works on this:

(setq y '(2 4 6 8))
(let ((x '(1 2 3 4)))
  (vset title (format "\"%s xpoints %s ypoints\"" (length x) (length y))))

set title "4 xpoints 4 ypoints"

I didn't think I would need to use eval, but I can't figure out another way to do it.

5
  • 1
    "with some parts of the form evaluated" - which parts? What should (let ((x 3)) (s x)) evaluate to?
    – npostavs
    May 3, 2017 at 17:54
  • should it really evaluate to a string?
    – sds
    May 3, 2017 at 18:20
  • @npostavs that is a good question. I guess you should expect it to evaluate to "3", but in the case I am working on this would not be allowed since by form I meant something in a list. I will add some clarifying points above. May 3, 2017 at 22:38
  • 1
    If you can't provide any rules for what should be evaluated and what shouldn't, I can't see how this question can get anywhere.
    – phils
    May 3, 2017 at 22:51
  • The rules are 1) if it is a symbol use the symbol name, and 2) if it is a form or satisfies listp (...) evaluate it. I could be on the wrong track. I can see this would have trouble with something like "plot sin(x)" where (x) looks like it should be evaluated, but in this case it wouldn't be. That could be fixed by (plot "sin(x)") where the string would avoid that issue. May 3, 2017 at 23:57

3 Answers 3

1

1) if it is a symbol use the symbol name, and 2) [otherwise] evaluate it.

(defmacro gset (&rest args)
  `(concat "set" ,@(cl-mapcan
                    (lambda (s)
                      (list " "
                            (if (symbolp s) (symbol-name s)
                              `(with-output-to-string
                                 (princ ,s)))))
                    args)))
3
  • This doesn't work. e.g. (gset title (+ 5 6)) gives a sequencp error on 11. Also, you need a mapconcat I think that puts a " " between the values. I edited my question with a solution that does work I think. May 4, 2017 at 1:42
  • @JohnKitchin oops, fixed.
    – npostavs
    May 4, 2017 at 12:00
  • I will accept this as doing what I set out to do, and for being a different way to solve it than I came up with. May 4, 2017 at 19:47
1

I'm not sure I understand what you really want to do: why do you want to call the macro with a single argument that is a list of three elements, rather than with three arguments? I'll take the spec to be that the macro must take a single argument, which must be a list of (at least) three elements, and the first two elements are implicitly quoted (not evaluated at all) while the third is evaluated when the macro-expanded code is executed.

(defmacro s (args)
  (let ((arg0 (nth 0 args))
        (arg1 (nth 1 args))
        (arg2 (nth 2 args)))
    `(format "%s %s %S" ',arg0 ',arg1 ,arg2)))

The macro is a function that returns code to execute. Here the code is a call to the format function, to build the string that you ultimately want. The first two arguments are quoted: ',arg0 takes the first element of the list and puts it inside quote, likewise with the second element. The third element is not quoted, so it is placed inside the code to execute.

Here's the same code expressed with explicit list building instead of using backquote and quote syntax.

(defmacro s (args)
  (let ((arg0 (nth 0 args))
        (arg1 (nth 1 args))
        (arg2 (nth 2 args)))
    (list 'format "%s %s %S" (list 'quote arg0) (list 'quote arg1) arg2)))

A useful way to check how a macro works is to call macroexpand.

(macroexpand '(s set title (+ 1 1)))

Another way is to define a function and look at its definition.

(lambda (x) (s (set title (+ 1 x))))
(lambda (x) (format "%s %s %S" (quote set) (quote title) (+ 1 x)))
3
  • It doesn't matter much if it is a form or separate args, either way the number of args or the number of things in the form is not fixed. So it would have to be defined like (defmacro s (&rest args) ...). If the number was fixed, I already know how to do this with commas and backquotes. May 3, 2017 at 22:28
  • @JohnKitchin You should mention important information like this in your question! So how do you distinguish the arguments that need to be expanded and the ones that mustn't be? May 3, 2017 at 22:43
  • I thought I had mentioned those when I noted the form could have any elements, and that I wanted to use symbol-names for anything symbolp, and evaluate any lists ;) I think I have a solution that seems to work, but it uses eval. I felt like that shouldn't have been necessary. May 4, 2017 at 0:14
1

This is a little variation on what I was hoping to achieve. What I wanted was to have a macro with selective evaluation of some arguments. This converts any keywords into the value of the corresponding symbol with no : on it. This avoids the use of eval that I used in the previous solution, basically by moving the eval out of the macro. In the body I just build up a let body that is used at runtime.

(defmacro kset (&rest args)
  `(let ,(loop for arg in args
           collect
           (if (keywordp arg)
           (let ((sym (intern (substring (symbol-name arg) 1))))
             (list sym (symbol-value sym)))
         (list arg (symbol-name arg))))
     (mapconcat
      (lambda (s)
    (format "%S"
        (if (keywordp s)
            (symbol-value  (intern (substring (symbol-name s) 1)))
          s)))
      ',args " ")))

Then, this code

(let* ((x 3)
       (ts (format "%s points" x))
       (tc (if (oddp x) "red" "blue")))
  (kset set title :ts textcolor :tc))

evaluates to

set title "3 points" textcolor "red"

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.