Formatted output
[Note: If you have comments please post them at the Prolog Community Discourse for this PIP]
Abstract
Define the common functionality of the widely implemented format/2,3 predicate.
Notation and Terminology
Abbreviations for Prolog systems: Ciao (Ciao-Prolog), ECL (ECLiPSe), GP (GNU-Prolog), IF (IF-Prolog), QP (Quintus Prolog), SP (SICStus Prolog), SWI (SWI-Prolog), XSB (XSB-Prolog).
Motivation and Rationale
The format/2,3 predicate provides a flexible way of producing formatted text from data, much in the spirit of C’s printf or Lisp’s format functions. It was probably first introduced by Quintus Prolog, but variants of it are present in several current Prolog systems.
Related, implementation-specific predicates are, for instance, IF’s write_formatted/2,3, ECL’s printf/2,3 and SWI’s writef/2,3.
The aim of this PIP is to improve portability by specifying minimal functionality, pointing out features that should be deprecated, while allowing for implementation-defined extensions.
Main section(s)
format(+Control, +Arguments)
format(+Stream, +Control, +Arguments)
Interprets the Arguments according to the Control string and prints the result on the current or specified output stream. Arguments:
Stream is the stream to which output is written. format/2 writes to the current output.
Control is a text (represented as atom, chars, codes or string), which may contain control sequences of the form
~NC, whereCis a (single-character) format control option, andNis its optional non-negative integer parameter. Any characters that are not part of a control sequence are written to the stream.Arguments is a list of arguments, which will be interpreted and possibly printed by format control options.
N can be omitted, or given in one of the
following ways:
- as a sequence of decimal digits
- as the character
*, in which caseNwill be taken as the next argument from Arguments (which must evaluate to a non-negative integer) - as `<character>, in which case
Nis the integer code of the given character (only really useful for~t)
If a format option accepts a parameter, but
N is omitted, a default value is assumed. If
a format option does not accept a parameter, but
N is given, N is ignored.
Format options
Printing Text
Formatting options for text, i.e. characters, atoms, or strings.
~a
argument is an atom, which is printed without quoting. Systems with a string type should accept both atom and strings.
~Nc
argument is an integer character code, and the corresponding character is printed N times. If N is omitted, it defaults to 1.
~Ns
argument is any text representation (list of numeric character codes, list of single-character atoms, atom, or string type). If N is given, exactly N characters are printed, truncating or space-padding the text to the right, as necessary. If N is omitted, it defaults to the length of the text, i.e. the text is printed in full.
~~
prints one ~.
Examples
?- format('~a', [foo]).
foo
?- format('~c', [97]).
a
?- format('~3c', [97]).
aaa
?- format('~s.', ["string"]).
string.
?- format('~3s.', ["string"]).
str.
?- format('~9s.', ["string"]).
string .
?- format('~~', []).
~
Printing Numbers
Formatting options for printing numbers.
NOTE regarding the float formats
~f,~F,~e,~E,~g,~G: If the corresponding
argument does not evaluate to a float (e.g. if it is
integer), it may be coerced to a float before being
printed, which may or may not imply a loss of
precision.
~Ne
argument is an arithmetic expression, whose value is
printed in exponential notation with precision N. The
output is the same as the one produced by the C library
function printf() with format "%.Ne". N+1
significant digits will be printed, one before the decimal
point, and N after. If N is omitted, it defaults to 6. If
N is 0, no decimal point or following digits are
printed.
~NE
same as ~Ne, except E is used for exponentiation instead of e.
~Nf
argument is an arithmetic expression, whose value is printed in non-exponential format, with N digits to the right of the decimal point. If N is omitted, it defaults to 6. If N is 0, no decimal point or following digits are printed.
~NF
same as ~Nf, except upper case is used for INF,
NAN.
NOTE: Not traditional, but %F is present in recent C
printf standards.
~Ng
argument is an arithmetic expression, whose value is printed in either ~Ne or ~Nf form, whichever gives the best precision in minimal space, with the exception that no trailing zeroes are printed unless one is necessary immediately after the decimal point to make the resultant number syntactically valid. At most N significant digits are printed. If N is omitted, it defaults to 6. A value of N<1 is treated like 1.
~NG
same as ~Ng, except E/F is used instead of e/f.
~Nd
argument is an arithmetic expression, whose value (which must be an integer), is printed as a signed decimal number, shifted right N decimal places. If N is omitted, it defaults to 0. If N is 0, no decimal point is printed.
~ND
same as ~Nd, except that commas are inserted to separate groups of three digits to the left of the decimal point.
~Nr
argument is an integer, which is printed in radix N (where 2 =< N =< 36, else error) using the digits 0-9 and the letters a-z. If N is omitted, it defaults to 8.
~NR
same as ~Nr, except it uses the digits 0-9 and the letters A-Z instead of a-z.
Examples
?- format('~3e', [16.66666]).
1.667e+01
?- format('~3f', [16.66666]).
16.667
?- format("~0f", [16.66666]).
17
?- format("~2f", [29]).
29.00
?- format('~3g', [16.66666]).
16.7
?- format('~g', [1000000000.0]).
1e+09
?- format('~20g', [1000000000.0]).
1000000000
?- format('~d', [29]).
29
?- format('~1d', [29]).
2.9
?- format('~D', [29876]).
29,876
?- format('~1D', [29876]).
2,987.6
?- format('~2r', [13]).
1101
?- format('~r', [13]).
15
?- format('~16r', [13]).
d
?- format('~16R', [13]).
D
Printing General Terms
Format options for inserting general terms.
~k
argument is passed to write_canonical/[1,2].
~p
argument is passed to print/[1,2].
~q
argument is passed to writeq/[1,2].
~w
argument is passed to write/[1,2].
~W
Stream and the next two list elements (Term, Options) are passed to write_term/3. This is a generalisation of the ~w, ~k, ~p, ~q options, allowing any term to be printed with any of the supported term output options.
Examples
?- format('~k', ['A'+'B']).
+('A','B')
?- asserta((portray(X+Y) :- write(X), write(' plus '), write(Y))).
?- format('~p', ['A'+'B']).
A plus B
?- format('~q', ['A'+'B']).
'A'+'B'
?- format('~w', ['A'+'B']).
A+B
?- format("Before ~W after", [a+'B', [quoted,ignore_ops]]).
Before +(a,'B') after
Columns and padding
The following control options manipulate column boundaries (tab positions). These column boundaries only apply to the line currently being written. An implicit column boundary is assumed to exist at the beginning of each line, i.e. in position 0.
~|
sets a column boundary at the current position.
~N|
sets a column boundary at position N and moves the cursor to that position. The required padding will be evenly distributed among all ~t’s between the previous and this column boundary.
~N+
sets a column boundary at N positions past the previous column boundary and moves the cursor to that line position. If N is omitted, it defaults to 8. The required padding will be evenly distributed among all ~t’s between the previous and this column boundary.
~Nt
defines a location where padding can be inserted.
Required padding between two consecutive column boundaries
will be distributed among all ~t’s between these
boundaries (as evenly as possible, such that each ~t has
the same or one more space than all the preceding ones).
If no ~t’s are present, all padding goes just before the
right column boundary.
If N is given, it is taken as the character code to be
used for the padding character (default is space).
Examples
?- format("#~|~t~a~t~a~t~a~t~10+#", [a,b,c]).
# a b c #
?- format("#~|~t~a~t~a~t~a~t~11|#", [a,b,c]).
# a b c #
?- format('~`*t NICE TABLE ~`*t~61|~n', []),
format('*~t*~61|~n', []),
format('*~t~a~20|~t~a~t~20+~a~t~20+~t*~61|~n',
['Right aligned','Centered','Left aligned']),
format('*~t~d~20|~t~d~t~20+~d~t~20+~t*~61|~n', [123,45,678]),
format('*~t~d~20|~t~d~t~20+~d~t~20+~t*~61|~n', [1,2345,6789]),
format('~`*t~61|~n', []).
************************ NICE TABLE *************************
* *
* Right aligned Centered Left aligned *
* 123 45 678 *
* 1 2345 6789 *
*************************************************************
Miscellaneous
~i
argument is ignored.
~Nn
prints N newline characters. If N is omitted, it defaults to 1.
~N
prints nothing if at the beginning of a line, otherwise prints one newline character.
Examples
?- format('~i', [10]).
?- format('begin~2nend', []).
begin
end
?- format('~Nbegin~N~Nend', []).
begin
end
Error handling
Errors should be raised in the following circumstances (the exact error term is left unspecified by this PIP):
- when Stream is not a stream
- when Control is not an atom, string, chars or codes list
- when Arguments is not a proper list
- when Arguments has less or more elements than expected based on the format string
- when the given argument type does not match the one expected according to the format string
- when the argument corresponding to a
*in the format string is not an integer-valued expression - when the number following
`in a format string is not a valid character code - when the radix in
~ror~Ris outside the range 2..36 - when
~is not followed by an valid format sequence
Non-conflicting Extensions
- Systems with a
stringdata type should accept this type in all places where general text (atom, chars, codes) is accepted. - Systems may support additional implementation-defined formats, or have a mechanism for user-defined formats.
- Systems may interpret an integer parameter in options where it would normally be ignored.
Recommendations for Deprecation
Existing functionality that, in the interest of portability, should probably be deprecated, or at least not replicated:
- Many systems (e.g. Ciao,SWI,XSB) allow a non-list term
for
Arguments, and interpret it as a list containing that single term. This should not be relied upon, as it creates ambiguities with arguments that are themselves lists. - SP allows negative N in some formats.
- Some systems in some places accept float expressions where integers are expected, and convert by truncation.
- In the
~eand~fformats, SP treats a precision of 0 as 1. - GP allows printf-compatible format specifiers of the
form
%Fto be mixed with~Fformats. - Some systems implement the
~@format, which interprets the next argument as a goal, executes it, and inserts any output written to the current output stream into format’s output. Such functionality should be factored out of format/2,3 as it creates a number of problems (dealing with goal failure, bindings, side effects, module context, meta-property of format’s argument list, etc).
Availability
Variants of format/2,3 are implemented, for instance, in QP, SP, GP as built-ins, or in Ciao, XSB, ECL as library modules.
Related and future work
See also PIP-0105 (write_term/2,3).
Future work
- options for using locales
- printing floats accurately
References
- [STD] International Standard ISO/IEC 13211-1 : 1995 Programming Languages - Prolog
- [QP] Quintus Prolog Manual https://quintus.sics.se
- [SP] SICStus Prolog Manual https://sicstus.sics.se
- [SWI] SWI Prolog Manual https://swi-prolog.org
- [Ciao] Ciao Prolog Manual https://ciao-lang.org
- [GP] GNU Prolog Manual http://gprolog.org
- [XSB] XSB Prolog Manual https://xsb.com/xsb-prolog and https://sourceforge.net/projects/xsb
- [ECL] ECLiPSe Manual https://eclipseclp.org
Copyright
This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License.

