Chapter 24 - FORMAT Speaks a Different Language

Throughout this book, we've shown some simple uses of FORMAT without explanation. In this chapter, we'll inventory and describe FORMAT's most useful capabilities, and a few of its more esoteric features.

FORMAT rhymes with FORTRAN, sort of...

FORMAT appears to have been inspired by FORTRAN's varied and capable function of the same name. But Lisp's FORMAT implements a programming language in its own right, designed expressly for the purposes of formatting textual output. FORMAT can print data of many types, using various decorations and embellishments. It can print numbers as words or -- for you movie buffs -- as Roman numerals. Columnar output is a breeze with FORMAT's facilities for iterating over lists and advancing to specific positions on an output line. You can even make portions of the output appear differently depending upon the formatted variables. This chapter covers a representative portion of what FORMAT can do; you should consult a Lisp reference to get the full story.

Formatting

FORMAT expects a destination argument, a format control string, and a list of zero or more arguments to be used by the control string to produce formatted output.

Output goes to a location determined by the destination argument. If the destination is T, output goes to *STANDARD-OUTPUT*. The destination can also be a specific output stream.

There are two ways FORMAT can send output to a string. One is to specify NIL for the destination: FORMAT will return a string containing the formatted output. The other way is to specify a string for the destination; the string must have a fill pointer.

? (defparameter *s* 
    (make-array 0 
                :element-type 'character 
                :adjustable t 
                :fill-pointer 0))
""
? (format *s* "Hello~%")
NIL
? *s*
"Hello
"
? (format *s* "Goodbye")
NIL
? *s*
"Hello
Goodbye"
? (setf (fill-pointer *s*) 0)
0
? *s*
""
? (format *s* "A new beginning")
NIL
? *s*
"A new beginning"

The call to MAKE-ARRAY with options as shown above creates an empty string that can expand to accommodate new output. As you can see, formatting additional output to this string appends the new output to whatever is already there. To empty the string, you can either reset its fill pointer (as shown) or create a new emtpy string.

FORMAT returns NIL except when the destination is NIL.

The format control string contains literal text and formatting directives. Directives are always introduced with a ~ character.

  Directive   Interpretation
  ---------   --------------
     ~%       new line
     ~&       fresh line
     ~|       page break
     ~T       tab stop
     ~<       justification
     ~>       terminate ~<
     ~C       character
     ~(       case conversion
     ~)       terminate ~(
     ~D       decimal integer
     ~B       binary integer
     ~O       octal integer
     ~X       hexadecimal integer
     ~bR      base-b integer
     ~R       spell an integer
     ~P       plural
     ~F       floating point
     ~E       scientific notation
     ~G       ~F or ~E, depending upon magnitude
     ~$       monetary
     ~A       legibly, without escapes
     ~S       READably, with escapes
     ~~       ~

The first few directives in the table above are for generating whitespace. A ~% directive inserts a newline character. ~& inserts a newline only if FORMAT output is not already at the beginning of a new line. ~| generates a page break character; not all devices are capable of responding to this character. You can cause FORMAT to emit multiple line or page breaks by using an optional argument, as in ~5% which generates five newlines.

To advance output to column n by inserting spaces, use ~nT.

You can justify output using ~< and ~> to enclose the output to be justified. ~w<text~> right-justifies text in a field of width n. If you segment text using ~; as a separator, the segments will be evenly distributed across the field of width w.

~C formats a character. Normally, the character formats as itself. However, if you modify the directive to ~:C, then non-printable characters are spelled out.

? (format nil "~:C" 7)  ;; 7 is ASCII BEL 
"Bell"

You can change the alphabetic case by enclosing output in ~( and ~). Different forms of the directive produce different results. ~(text~) converts text to lower case. ~:@(text~) converts text to upper case. ~:(text~) capitalizes each word in text. ~@(text~) capitalizes the first word in text and converts the rest to lower case.

The ~D directive formats an integer as decimal digits with a leading minus sign if the number is negative. ~wD right-justifies the output in a field of width w, padding on the left with spaces. ~w,'cD pads on the left with the character c. Adding the @ modifier immediately after the ~ causes a leading plus sign to be emitted for positive numbers as well as the leading minus sign that is always emitted for negative numbers. A : modifier causes commas to be inserted between each group of three digits.

You can format numbers in binary (base 2), octal (base 8) or hexadecimal (base 16) radix using the directives ~B, ~O, and ~X, respectively. Except for the radix, these behave exactly as ~D.

The ~R directive has two forms. ~bR prints an integer in base b. The directive ~10R is identical to ~D. Optional values should be specified following the radix. For example, ~3,8R prints a base-3 number in a field of width 8.

Without the radix specifier, ~R spells out an integer as a cardinal (counting) number in English.

? (format nil "~R" 7)
"seven"
? (format nil "~R" 376)
"three hundred seventy-six"

~:R spells out an ordinal (positional) number.

? (format nil "~:R" 7)
"seventh"

You can even print Roman numerals using ~@R.

? (format nil "~@R" 1999)
"MCMXCIX"

One of the hallmarks of professional-looking output is getting plurals right. Lisp makes this easy with the ~P format directive. Unless its argument is 1, ~P formats an "s". ~@P formats a "y" if its argument is 1, or an "ies" if the argument is not 1. Since these operations so commonly follow the printing of a number, ~:P and ~:@P reuse the previously consumed argument.

? (format nil "~D time~:P, ~D fl~:@P" 1 1)
"1 time, 1 fly"
? (format nil "~D time~:P, ~D fl~:@P" 3 4)
"3 times, 4 flies"

You can print floating-point numbers in four different ways:

~F
As a number with a decimal point, e.g. 723.0059
~E
In scientific notation, e.g. 7.230059E+2
~G
As the shorter of the above two representations, followed by a tab to align columns
~$
As a monetary value, e.g. 723.01

There are many options for printing floating point numbers. The most common is the field-width specifier, which behaves as it does in the ~D directive.

Non-numeric Lisp data is printed using the ~A and ~S directives. Use ~A when you want to print data in its most visually attractive form; that is, without the escape characters that would let the data be read correctly by READ. Using ~A, strings are formatted without quotes, symbols are formatted without package prefixes or escapes for mixed-case or blank characters, and characters are printed as themselves. Using ~S, every Lisp object is printed in such a way that READ can reconstruct the object (unless, of course, the object does not have a readable representation).

Iteration

List elements can be formatted using the directive ~{format-control~}. The argument must be a list; format-control consumes elements of the list. Iteration terminates when the list is empty before the next pass over format-control. Here's a simple example:

? (format t "~&Name~20TExtension~{~&~A~20T~A~}"
          '("Joe" 3215 "Mary" 3246 "Fred" 3222 "Dave" 3232 "Joseph" 3212))
Name                Extension
Joe                 3215
Mary                3246
Fred                3222
Dave                3232
Joseph              3212
NIL

If your list might end in the middle of the iteration's format-control, you can insert a ~^ directive at that point. If the argument list is empty when interpreting the ~^ directive, the iteration ~{format-control~} terminates at that point.

Additional options let you limit the number of iterations and specify different requirements for the arguments. Consult a Lisp reference for details.

Conditionals

Conditional format directives are introduced by ~[ and delimited by ~]. There are several forms, which I'll call ordinal, binary, and conditional. The ordinal form is ~[format-0~;format-1~;...~;format-N~], which selects the format-Ith clause for an argument value of I.

? (format t "~[Lisp 1.5~;MACLISP~;PSL~;Common Lisp~]" 2)
PSL
NIL

Within ~[ and ~] you can specify a final default clause as ~:;format-default; this is selected if the argument is outside the range 0 to N.

The binary form is written ~:[format-false~;format-true~]. The format-false clause is interpreted if the argument is NIL; otherwise, the format-true clause is interpreted.

? (format t "My computer ~:[doesn't~;does~] like Lisp." t)
My computer does like Lisp.
NIL
? (format t "My computer ~:[doesn't~;does~] like Lisp." nil)
My computer doesn't like Lisp.
NIL

The conditional form, written as ~@[format~], first tests its argument. If the argument is not NIL, it is not consumed; rather, it is left for format to consume. If the argument is NIL, then it is consumed and format is not interpreted.

? (format nil "~{~@[~A ~]~}" '(1 2 nil 3 t nil 4 nil))
"1 2 3 T 4"

Floobydust

As you've seen, many of the format directives accept optional parameters, such as the field width parameter of the justification, tabbing, and numeric directives. Our examples have encoded these parameters into the format control string. Sometimes it is useful to have a parameter vary during the program's operation. You can do this by specifying V where the parameter would appear; the parameter's value is then taken from the next argument in the argument list.

? (format t "~{~&~VD~}" '(5 37 10 253 15 9847 10 559 5 12))
   37
       253
           9847
       559
   12
NIL

In this example, the arguments are consumed in pairs, with the first of each pair specifying a field width and the second being the number to print.


Contents | Cover
Chapter 23 | Chapter 25
Copyright © 1995-1999, David B. Lamkins
All Rights Reserved Worldwide

This book may not be reproduced without the written consent of its author. Online distribution is restricted to the author's site.