Search code examples
macroslispcommon-lisp

Using a macro ends in error when using a special variable


SBCL 2.3.7

I want to save me from writing a format like this because I have a lot of variables (more than 3) and want to insert a semicolon between them:

(format nil "~a;~b;~c;" var1 var2 var3)

Expected result:

"a;b;c;"

I have defined a special variable and a macro.

(defparameter *delimiter* ";")

(defmacro format-delimited (stream fmt delimiter &body vars)
   (let ((fmtstr ""))
    (dotimes (i (length vars))
      (setf fmtstr (str:concat fmtstr fmt delimiter)))
    `(format ,stream ,fmtstr ,@vars)))

This call successfully returns:

(format-delimited nil "~a" ";" "a" "b" "c")
"a;b;c;"

macroexpand-1 returns:

(FORMAT NIL "~a;~a;~a;" "wolf" "golf" "rolf")

This call ends in error:

(format-delimited nil "~a" *delimiter* "a" "b" "c")

; Debugger entered on #<TYPE-ERROR expected-type: SEQUENCE datum: *DELIMITER*>

macroexpand-1 also ends in error.

What is the cause of the error?


Solution

  • I see you use str:concat, so you can use (str:join ";" '("wolf" "golf")) (you don't need a macro).

    str:join takes two arguments: the delimiter and a list of strings. If you want to write a varying number of strings without a list, like so:

    (join-all ";" "wolf" "golf" "rolf" etc etc etc)
    

    we can write a helper function. It will accept &rest arguments:

    (defun join-all (delimiter &rest strings)
         (funcall #'str:join delimiter strings))
    

    =>

    (JOIN-ALL ";" "golf" "wolf" "rolf")
    "golf;wolf;rolf"
    

    funcall allows to call a function by name with its right number of arguments. The varying number of strings are grouped as a list thanks to &rest strings, so we can pass this list as argument to str:join.


    • the "str" library: https://github.com/vindarel/cl-str/