to break out of the iteration without printing the comma and space.
With an at-sign modifier, ~{
processes the remaining format arguments as a list.
(format nil '~@{~a~^, ~}' 1 2 3) ==> '1, 2, 3'
Within the body of a ~{...~
}, the special prefix parameter #
refers to the number of items remaining to be processed in the list rather than the number of remaining format arguments. You can use that, along with the ~[
directive, to print a comma-separated list with an 'and' before the last item like this:
(format nil '~{~a~#[~;, and ~:;, ~]~}' (list 1 2 3)) ==> '1, 2, and 3'
However, that doesn't really work right if the list is two items long because it adds an extra comma.
(format nil '~{~a~#[~;, and ~:;, ~]~}' (list 1 2)) ==> '1, and 2'
You could fix that in a bunch of ways. The following takes advantage of the behavior of ~@{
when nested inside another ~{
or ~@{
directive—it iterates over whatever items remain in the list being iterated over by the outer ~{
. You can combine that with a ~# [
directive to make the following control string for formatting lists according to English grammar:
(defparameter *english-list*
'~{~#[~;~a~;~a and ~a~:;~@{~a~#[~;, and ~:;, ~]~}~]~}')
(format nil *english-list* '()) ==> ''
(format nil *english-list* '(1)) ==> '1'
(format nil *english-list* '(1 2)) ==> '1 and 2'
(format nil *english-list* '(1 2 3)) ==> '1, 2, and 3'
(format nil *english-list* '(1 2 3 4)) ==> '1, 2, 3, and 4'
While that control string verges on being 'write-only' code, it's not too hard to understand if you take it a bit at a time. The outer ~{...~
} will consume and iterate over a list. The whole body of the iteration then consists of a ~#[...~]
; the output generated each time through the iteration will thus depend on the number of items left to be processed from the list. Splitting apart the ~#[...~]
directive on the ~;
clause separators, you can see that it's made up of four clauses, the last of which is a default clause because it's preceded by a ~:;
rather than a plain ~;
. The first clause, for when there are zero elements to be processed, is empty, which makes sense—if there are no more elements to be processed, the iteration would've stopped already. The second clause handles the case of one element with a simple ~a
directive. Two elements are handled with '~a and ~a'
. And the default clause, which handles three or more elements, consists of another iteration directive, this time using ~@{
to iterate over the remaining elements of the list being processed by the outer ~{
. And the body of that iteration is the control string that can handle a list of three or more elements correctly, which is fine in this context. Because the ~@{
loop consumes all the remaining list items, the outer loop iterates only once.
If you wanted to print something special such as '<empty>' when the list was empty, you have a couple ways to do it. Perhaps the easiest is to put the text you want into the first (zeroth) clause of the outer ~# [
and then add a colon modifier to the closing ~
} of the outer iteration—the colon forces the iteration to be run at least once, even if the list is empty, at which point FORMAT
processes the zeroth clause of the conditional directive.
(defparameter *english-list*
'~{~#[<empty>~;~a~;~a and ~a~:;~@{~a~#[~;, and ~:;, ~]~}~]~:}')
(format nil *english-list* '()) ==> '<empty>'
Amazingly, the ~{
directive provides even more variations with different combinations of prefix parameters and modifiers. I won't discuss them other than to say you can use an integer prefix parameter to limit the maximum number of iterations and that, with a colon modifier, each element of the list (either an actual list or the list constructed by the ~@{
directive) must itself be a list whose elements will then be used as arguments to the control string in the ~:{...~
} directive.
A much simpler directive is the ~*
directive, which allows you to jump around in the list of format arguments. In its basic form, without modifiers, it simply skips the next argument, consuming it without emitting anything. More often, however, it's used with a colon modifier, which causes it to move backward, allowing the same argument to be used a second time. For instance, you can use ~:*
to print a numeric argument once as a word and once in numerals like this:
(format nil '~r ~:*(~d)' 1) ==> 'one (1)'
Or you could implement a directive similar to ~:P
for an irregular plural by combing ~:*
with ~[
.
(format nil 'I saw ~r el~:*~[ves~;f~:;ves~].' 0) ==> 'I saw zero elves.'
(format nil 'I saw ~r el~:*~[ves~;f~:;ves~].' 1) ==> 'I saw one elf.'
(format nil 'I saw ~r el~:*~[ves~;f~:;ves~].' 2) ==> 'I saw two elves.'
In this control string, the ~R
prints the format argument as a cardinal number. Then the ~:*
directive backs up so the number is also used as the argument to the ~[
directive, selecting between the clauses for when the number is zero, one, or anything else.[199]
Within an ~{
directive, ~*
skips or backs up over the items in the list. For instance, you could print only the keys of a plist like this:
(format nil '~{~s~*~^ ~}' '(:a 10 :b 20)) ==> ':A :B'
The ~*
directive can also be given a prefix parameter. With no modifiers or with the colon modifier, this parameter specifies the number of arguments to move forward or backward and defaults to one. With an at-sign modifier, the prefix parameter specifies an absolute, zero-based index of the argument to jump to, defaulting to zero. The at-sign variant of ~*
can be useful if you want to use different control strings to generate different messages for the same arguments and if different messages need to use the arguments in different orders.[200]
And there's more—I haven't mentioned the ~?
directive, which can take snippets of control strings from the format arguments or the ~/
directive, which allows you to call an arbitrary function to handle the next format argument. And then there are all the directives for generating tabular and pretty-printed output. But the directives discussed in this chapter should be plenty for the time being.
In the next chapter, you'll move onto Common Lisp's condition system, the Common Lisp analog to other languages' exception and error handling systems.
19. Beyond Exception Handling: Conditions and Restarts
One of Lisp's great features is its