Formatted output
<-Previous | ^UP^ | Next->
When we want more control, flexibility and sophistication with text output we use the format function
below are some of the most commonly used and useful features of format, but it can sometimes be difficult to understand or read, due to its compactness and its non-Lispy syntax. A great resource on the Internet is an article by Peter Seibel, called a few FORMAT recipes which provides a detailed discussion on the features and uses of format and some very useful examples. See the link in the resources section
Format takes 2 required arguments
- A destination for its output
- A control string that will generally contain literal text, but will always contain formatdirectives
Output Destination
The output destination is the first argument to format. There are 4 types of output destination, but we'll discuss just the first 3
- If the value is T, the output destination is the *standard-output* stream
- If the value is NIL, the output is generated as a string and returned by format
- If the destination is a stream, the output will be written to that stream
Control String
The control string may look complex because it is based on characters, not s-expressions, and is optimised for compactness. In many ways it is another mini-programming language within Lisp. As well as containing literal text, the control string contains format directives.
- All directives start with a tilde (~) character, and end with a single character. This character can be either upper of lower case
- Some directives take prefix parameters, between the tilde and the character. If more than one prefix parameter is used they are separated by commas (,). Prefix parameters give additional output control
- Some directives use either the colon (:) or at-sign (@) modifiers which change the behavior of the directive in small ways
- There are also some special directives which are used in pairs and can be wrapped around other directives, e.g. to control capitalisation ( ~( ~) ), conditional formatting (~[ ~]) or iteration (~{ ~})
Basic Formatting
The most basic directive is ~a. It consumes one of the format arguments and outputs it in human-readable form. Note that because we are using NIL as the output destination, format is returning a string as return-value and not outputting anywhere.
Another basic directive is ~%, which causes a newline to be emitted. It optionally takes a single prefix parameter that defines how many newlines to be emittedGDL-USER> (setq text "Hello")
"Hello"GDL-USER> (setq number 123.456)
123.456GDL-USER> (setq my-list (list 1 2 3))
(1 2 3)GDL-USER> (format nil "~a" text)
"Hello"GDL-USER> (format nil "~a" number)
"123.456"GDL-USER> (format nil "~a" my-list)
"(1 2 3)"GDL-USER> (format nil "Line 1~%Line2")
"Line 1Line 2"GDL-USER> (format nil "Line 1~%Line2")
"Line 1Line 2"
Integer Directives
Whist the directive ~a can be used to output numbers, the directive ~d offers more control for outputting integers (d standing for decimal, or base 10). There are 2 modifiers:
- A colon (:) adds commas separating the number into groups of 3 integers
- An at-sign (@) always prints a sign before the number
- These 2 modifiers may be combined
- The first prefix parameter specifies the minimum width for the output
- The second prefix parameter specifies the padding character. By default this is a space. Padding characters must be quoted are always inserted before the number
GDL-USER> (format nil "~d" 1234567)
"1234567"GDL-USER> (format nil "~:d" 1234567)
"1,234,567"GDL-USER> (format nil "~@d" 1234567)
"+1234567"GDL-USER> (format nil "~:@d" 1234567)
"+1,234,567"GDL-USER> (format nil "~12d" 1234567)
" 1234567"GDL-USER> (format nil "~12,'0d" 1234567)
"000001234567"GDL-USER> (format nil "~2,'0d-~2,'0d-~d" 31 7 2022)
"31-07-2022"
Floating Point Directives
The 2 principle directives handling floating point numbers are ~f and ~e. The difference is ~f is alowed to use scientific notation if the number is large enough or small enough, whilst ~e will always emit the number argument in scientific notation.
There are a number of prefix parameters, but the only one of real significance is the second, which specifies the number of digits to output after the decimal point. Note that, if this prefix parameter is less than the number of decimal digits in the argument, the argument will be mathematically rounded.
A third floating point number directive is ~$ which is a monetary directive. It is basically equivalent to ~f with the second prefix parameter dafaulting to 2
GDL-USER> (format nil "~f" pi)
"3.141592653589793"GDL-USER> (format nil "~,3f" pi)
3.142GDL-USER> (format nil "~e" pi)
"3.141592653589793E+0"GDL-USER> (format nil "~$" pi)
3.14
English Language Directives
These directives are useful for converting number to english language, outputting plurals and performing case conversions
- ~r prints numbers as english words. With the : modifier it prints the number as an ordinal
- ~p pluralises a word, emitting an s character when the argument is anything but 1. It is often used with the : modifier which makes it reprocess the previous format argument. Using the @ modifier causes a y character to be emitted when the format argument is 1, or ies to be emitted for other values
GDL-USER> (format nil "computer~p" 1)
"computer"GDL-USER> (format nil "~r computer~:p" 1)
"one computer"GDL-USER> (format nil "~r computer~:p" 2)
"two computers"GDL-USER> (format nil "~r fl~:@p" 1)
"one fly"GDL-USER> (format nil "~r fl~:@p" 2)
"two flies"
To control case we use the ~( directive paired with a ~) in conjunction with the 2 modifiers : and @
- Without either modifier the output is all lower case
- With the @ modifier the first word is the string between the ~( and ~) directives is capitalised
- With the : modifier all words is the string between the ~( and ~) directives are capitalised
- With both modifiers the output is all upper case
GDL-USER> (setq txt "sOme RANdom TexT")
"sOme RANdom TexT"GDL-USER> (format nil "~(~a~)" txt)
"some random text"GDL-USER> (format nil "~@(~a~)" txt)
"Some random text"GDL-USER> (format nil "~:(~a~)" txt)
"Some Random Text"GDL-USER> (format nil "~:@(~a~)" txt)
"SOME RANDOM TEXT"
Iteration
The ~{ directive paired with~} makes format itterate over the elements of a list. The control string between these 2 directives will be repeatedly processed as long as there are elements left in the list.
In many cases we may wish to add some text seperators, for example a comma between the elements in the list, but avoid a final seperator. To do this we use the ~^ directive before the seperator. By using the ~@ modifier, any remaining format arguments are treated as a list
GDL-USER> (format nil "~{~a, ~}" (list 1 2 3))
"1 2 3 "GDL-USER> (format nil "~{~a ~}" (list 1 2 3))
"1, 2, 3, "GDL-USER> (format nil "~{~a~^,~}" (list 1 2 3))
"1, 2, 3"GDL-USER> (format nil "~@{~a~^,~}" 1 2 3)
"1, 2, 3"
Conditionals
The ~[ directive paired with~] provides a simple control construct. Inbetween these directive are a number of clauses seperated by ~; and the argument supplied represents the index number (0 based) of the clasue to be used. If the index number is bigger then the number of clauses nothing is printed, unless the last clause is seperated by ~:; in which case this clause is used as the default if no match is found
With the ~@ modifier the control string between ~[ and ~] is only emitted if the format argument is non-NIL
GDL-USER> (format nil "~[Peter~;Paul~;John~]" 0)
"Peter"GDL-USER> (format nil "~[Peter~;Paul~;John~]" 4)
""GDL-USER> (format nil "~[Peter~;Paul~;;John~]" 4)
"John"GDL-USER> (format nil "~@[~r cat~:p ~]~@[~r dog~:p~]" nil 2)
"two dogs"GDL-USER> (format nil "~@[~r cat~:p ~]~@[~r dog~:p~]" 3 nil)
"three cats "GDL-USER> (format nil "~@[~r cat~:p ~]~@[~r dog~:p~]" 3 1)
"three cats one dog"
Resources
Format Recipes | |
format.lisp |