Reputation: 477
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
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
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