In Common Lisp FORMAT how does recursive formatting work
Asked Answered
C

1

1

I need to generate a space-padded string with a variable string length. The not-so-clever solution that works involved nesting of format:

(format nil (format nil "~~~d,,a" 10) "asdf")

Now, I wanted to make it a bit more clever by using format's ~? for recursive processing. I would expect that something like this should do what I want:

(format nil "~@?" "~~~d,,a" 10 "asdf")

but what I get is just the formatting string, i.e. ~10,,a, not the padded asdf string. Perhaps I misunderstood the word 'recursive' here, but I would expect that having formed the inner format string, CL should proceed to actually use it. Am I missing something?

Cylindroid answered 19/2, 2018 at 14:44 Comment(7)
Use v in the control string: (format nil "~va" 10 "asdf")Giglio
Cool, thanks. This solves my particular case. But it got me thinking about a trickier use. Say, you not only want to parameterize the amount of padding, but also what you're padding with and you'd like to arrive at a formatting string of this form: (format nil "~10,,,'-a" "asdf"). Any chance of achieving it using ~???Cylindroid
(format nil "~v,,,va" 10 #\- "asdf")Otherworldly
You can have multiple vs: (format nil "~v,,,va" 10 #\- "asdf"). You misunderstand the use case for ~?. It's mainly meant for inserting another "call" to format with a different control string and arguments in a control string. For example, to prompt for a yes or no, you might have a function like (defun y-or-n-prompt (control-string &rest args) (format t "~&~? [y/n]: " control-string args))Giglio
@Giglio I see. My impression was that apart from formatting a substring recursively, format also processes this substring.Cylindroid
Generally, building control strings for FORMAT at run time is a bad idea. It's error prone and if you use a constant control string, implementations may process it at compile time rather than run time (producing more efficient code).Giglio
@Giglio : looks like this is worth an answer...Otherworldly
G
5

Variable arguments to format directives

FORMAT allows you to use v as an argument to a directive in the control string to pop arguments from the argument list.

CL-USER> (format nil "~va" 10 "asdf")
"asdf      "

There may be multiple vs used.

CL-USER> (format nil "~v,,,va" 10 #\- "asdf")
"asdf------"

Recursive processing with ~?

The recursive processing directive is meant for embedding a different "call" to FORMAT inside a control string. For example, a function to prompt for a yes or no answer might be implemented with it.

(defun y-or-n-prompt (control-string &rest args)
  (format t "~&~? [y/n]: " control-string args)
  ;;...
  )

The caller can now format a prompt with this as they would with FORMAT without having to worry about the details of what the prompt should look like to the user (adding a new line at the beginning or the [y/n]: prompt at the end).

CL-USER> (y-or-n-prompt "foo ~d bar" 12)
foo 12 bar [y/n]: 
NIL

The result of ~? will not be processed by FORMAT, so it cannot be used to build a control string. In general, building control strings at run time is a bad idea, because it's error prone (for example, you must escape any unwanted tildes in the string) and prevents the implementation from processing the control string at compile time.

Giglio answered 19/2, 2018 at 18:6 Comment(2)
Thanks for and exhaustive answer.Cylindroid
(...) and prevents the implementation from processing the control string at compile time. – This is true for any dynamic format string, whether you use the ~? recursive directive or format itself. But the user still has the option to compile a format string to a format control at runtime with formatter, and decide to cache it at will.Purificator

© 2022 - 2024 — McMap. All rights reserved.