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 predicates were probably first introduced by Quintus Prolog, but variants of it are present in several current Prolog systems.
Related predicates are IF’s write_formatted/2,3 and ECL’s printf/2,3, which are both closer to C printf syntax and functionality, but only implemented in the respective systems.
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 format control option, andNis its optional non-negative integer argument. 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.
If N can be specified, then it can be a
              sequence of decimal digits. Alternatively, it can be the
              character *, in which case N
              will be taken as the next argument from Arguments (which
              must be an integer). It can also begiven as
              `<character>, in which case N is the
              integer code of the given character (only useful for
              ~t).
Availability: in QP,SP,GP as builtin. In
              Ciao,XSB,ECL in library module format.
              NOTE: Many systems (Ciao,SWI,XSB,ECL) allow a non-list
              term for Arguments, and interpret it as a
              list containing that single term.
              NOTE: SP allows negative N in some formats!
              NOTE: GP also allows printf-compatible format
              specifiers of the form %F.
              NOTE: SWI also the set of format options to be
              extended.
              NOTE: In QP, Control could be atom or codes. In SP
              chars, codes, or atom. In SWI, chars, codes, atom or
              string.
Format options
Printing Text
Formatting options for text, i.e. characters, atoms, or strings.
~a
argument is an atom, which is printed without
              quoting.
              NOTE: QP allows the form ~Na where a
              maximum number of N characters is printed. SWI and ECL
              accept both atom and string arguments.
~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), from which at most the first N characters
              are printed. If N is zero or if N is omitted, it defaults
              to the length of the string. If the string is shorter than
              N then it is printed in full.
              NOTE: the traditional behaviour (QP, SP, GP, Ciao,
              XSB) is to accept only character code lists.
              TBD: Slight confusion about whether the output should
              be padded with spaces when the string is shorter than N
              characters. SP, Ciao, GP, XSB do pad. Original QP
              documentation does not mention padding, and SWI doesn’t
              pad. None of the other simple type formats do padding, as
              padding functionality is provided by ~t.
~~
prints one ~.
Examples
?- format('~a', foo).
foo
?- format('~c', 97).
a
?- format('~3c', 97).
aaa
?- format('~s', ["string"]).
string
?- format('~3s', ["string"]).
str
?- format('~~', []).
~
              Printing Numbers
Formatting options for printing numbers.
~Ne
argument is a floating-point number, which 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 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.
              NOTE: QP,SP,SWI,Ciao allow integer arguments. QP
              limits output to 60 digits. SP treats N<1 like
              N=1.
~NE
same as ~Ne, except E is used for exponentiation instead of e.
~Nf
argument is a floating-point number, which 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.
              NOTE: QP,SP,SWI,Ciao allow integer arguments. QP
              limits output to 60 digits. SP treats N<1 like
              N=1.
~NF
same as ~Nf, except upper case is used for INF,
              NAN.
              NOTE: Not traditional, but present in newer C printf
              standard.
~Ng
argument is a floating-point number, which 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.
              NOTE: SP always prints a decimal point and at least
              one digit after. QP,SP,SWI,Ciao allow integer arguments.
              QP limits output to 60 digits.
~NG
same as ~Ng, except E/F is used instead of e/f.
~Nd
argument is an integer, which is printed as a signed decimal number, shifted right N decimal places. If N is omitted, it defaults to 0. If N is 0, the decimal point is not 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) 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).
1.667
?- format("~0f", [16.66666]).
17
?- 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 output options.
              NOTE: Not traditional. SWI defines it in write_term/2
              instead of write_term/3.
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. A column boundary is initially assumed to be in line 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 evenly distributed among all ~t’s between these boundaries (or just before the right column boundary, if no ~t’s are present). If N is given, it is taken as the character code to be used for the padding character (default is space).
Examples
?- 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.
~@
the next argument is interpreted as a goal, will be
              executed as if by + + Goal (such that any bindings are
              discarded). Output written to the current output stream is
              inserted in format’s output. The behaviour with other side
              effects is undefined. Goal is called in the module calling
              format/3.
              TBD: Omit this? Not in QP, Ciao, GP, XSB. Concerns
              have been raised because the presence of goals in the
              argument list makes it sometimes a meta-argument.
              TBD: Behaviour on Goal failure?
Examples
?- format('~i', 10).
?- format('begin~2nend', []).
begin
end
?- format('~Nbegin~N~Nend', []).
begin
end
?- format('Hello ~@ world!\n', [write(new)]).
Hello new world!
              Points to clarify
Should arithmetic expressions be allowed in Arguments, and if so, where? Most systems seem to allow them in ~d,D,e,E,f,g,G,r,R, but not in ~c.
Dubious SP functionality: “If the parameter is specified as *, then the value used will be the truncated integer value of the next element from Arguments interpreted as a numerical expression.”
Which errors to raise:
- when the given argument type does not match the one expected according to the format string. Ciao invalid_arguments/1, SWI format_argument_type/2
 - ignore or error when N given but not supported: Ciao error, SWI ignores.
 - when extra arguments are given: GP,Ciao ignores, SWI error.
 
Additional formats: SP and SWI support
              ~h/H for precise floats, SWI also
              ~I for integers.
Related work
See also PIP-0105 (write_term/2,3).
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.

