jue
jue

Reputation: 477

split format string of (format t ...)

Sometimes I like to output some text with (format t ..).
To prevent long unreadable format-strings in source code, and get the output easily aligned, I use (format t (concatenate 'string ....).

Example:

(format t (concatenate 'string 
                       "some output~%"
                       "  error-msg: ~a~%"
                       "  uiop-cwd: ~a~%"
                       "  uiop-file-exists: ~a~%")
        "error foo"
        (uiop:getcwd)
        (uiop:file-exists-p "hello_world.bmp"))

Is there a more idiomatic and at-compile-time way to do the same in Common Lisp?

Upvotes: 2

Views: 483

Answers (2)

coredump
coredump

Reputation: 38809

Here is an equivalent format string that makes use of the Tilde newline format directive, which ignores the following newline and spaces (until the next visible character). In order to indent with spaces as you did, I wrote the forced newline ~% before the spaces:

(format t 
        "some output~
       ~%   error-msg: ~a~
       ~%   uiop-cwd: ~a~
       ~%   uiop-file-exists: ~a~%"
        "error foo"
        (uiop:getcwd)
        (uiop:file-exists-p "hello_world.bmp"))

(NB. This is a single string so there is no concatenation to be done at compile-time.)

Upvotes: 6

user5920214
user5920214

Reputation:

You can do quite well with something like:

(defun fmt (to control/s &rest args-to-format)
  (declare (dynamic-extent args-to-format)) ;?OK
  (apply #'format to (if (listp control/s)
                         (apply #'concatenate 'string control/s)
                       control/s)
         args-to-format))

(define-compiler-macro fmt (&whole form to control/s &rest args-to-format)
  (cond
   ((stringp control/s)
    `(format ,to ,control/s ,@args-to-format))
   ((and (listp control/s)
         (eql (first control/s) 'quote))
    ;; literal
    (destructuring-bind (_ thing) control/s
      (declare (ignore _))
      (print "here")
      (if (and (listp thing) (every #'stringp thing))
          `(format ,to ,(apply #'concatenate 'string thing) ,@args-to-format)
        form)))
   (t
    form)))

The compiler macro should ensure that the common case of

(fmt t '("~&foo: ~S~%"
         "bar~%") ...)

will have no run-time cost at all.

Upvotes: 3

Related Questions