[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
Issue: STREAM-INFO (Version 4)
- To: masinter.pa@xerox.com
- Subject: Issue: STREAM-INFO (Version 4)
- From: dick@wheaties.ai.mit.edu (Richard C. Waters)
- Date: Fri, 23 Sep 88 12:12:45 EDT
- Cc: gls@think.com, CL-Cleanup@sail.stanford.edu
- In-reply-to: masinter.pa@xerox.com's message of 20 Sep 88 11:35 PDT <880920-113534-4339@Xerox>
----------
Issue: STREAM-INFO
References: FORMAT ~T (pp398-9) and ~<...~> (pp404-6), PPRINT (p383)
Category: ADDITION
Edit history: 22-Jun-88, Version 1 by Pitman (2d model)
23-Jun-88, Version 2 by Waters (1d model, modified 2d model)
24-Jun-88, Version 3 by Pitman (minor reformatting)
24-Jun-88, Version 4 by Pitman (remove 2d model for submission)
23-Sep-88, Version 5 by Waters (cleaned up in response to discussion)
Status: For Internal Discussion
Problem Description:
Currently, there is no portable way to inquire about the line width of
an output stream, the current position on a line, or the amount of
space that the display of a string will take up. This makes it
essentially impossible to write a portable implementation of a pretty
printer.
Proposal (STREAM-INFO: ONE-DIMENSIONAL-FUNCTIONS):
Introduce four new functions.
These functions are carefully designed with an eye to the way they
interact. As a result, they can only be defined fully in terms of
each other. The presentation below first gives a very brief
definition of each function and then gives detailed specifications of
their relationships.
LINE-WIDTH &optional (OUTPUT-STREAM *STANDARD-OUTPUT*) [Function]
Returns a non-negative integer representing the line width available
when printing to OUTPUT-STREAM. If the stream has no meaningful
width (or the width cannot be computed) then NIL is returned.
LINE-POSITION &optional (OUTPUT-STREAM *STANDARD-OUTPUT*) [Function]
Returns a non-negative integer representing the current horizontal
position on the current output line, or NIL if the position cannot
be computed.
WRITE-SPACE WIDTH &optional (OUTPUT-STREAM *STANDARD-OUTPUT*) [function]
Inserts blank space of length WIDTH into OUTPUT-STREAM. WIDTH must
be a non-negative integer. WRITE-SPACE returns T if the operation
is successful and NIL otherwise (e.g., if the operation is not
supported by OUTPUT-STREAM).
PRINTED-WIDTH STRING &optional (OUTPUT-STREAM *STANDARD-OUTPUT*)
&key (START 0) (END NIL) [Function]
Returns an integer representing the horizontal width that would be
required to display STRING if it were written at the current moment
to OUTPUT-STREAM using (WRITE-STRING STRING OUTPUT-STREAM :start
START :end END), or NIL if this width cannot be computed. The width
may be negative (e.g., if STRING contains backspace or newline
characters.)
PRINTED-WIDTH does not return any indication of the vertical
distance required when printing STRING. The START and END
parameters delimit a substring of STRING in the usual manner.
PRINTED-WIDTH never causes any change in the state of OUTPUT-STREAM.
The width returned may well depend on the current state of
OUTPUT-STREAM (e.g., the width of tabs depends on the line position
and the width of characters depends on the font in use.) In all
respects the width is computed based on the current state of the
stream. However, the width returned always assumes that the total
line width is infinite---i.e., does not reflect any wraparound or
truncation which might occur.
-The difficulties of a full specification:
The functions above are intended to solve a specific current problem
in CL. To serve this purpose, they must have reasonably precise
specifications. However, there are several things which make it
desirable to have specifications which allow for significant
variability between implementations. First, current implementations
of CL differ greatly in the way IO is supported, and overly strict
specifications might make things very difficult for certain
implementations. Second, CL places no limits on the kinds of
idiosyncratic characters which can be supported by particular
implementations. Third, while many CL implementations only support
the printing of characters in fixed width fonts, it is desirable to
allow for output streams that support variable width fonts.
Finally, it is desirable to leave room to move for the future.
-Operations on standard characters where the line-width has not yet been exceeded.
To deal with the problems above, a layered specification is
provided. The lowest level specification is given in terms of
constraints between the four functions above. In this lowest level
specification, two key simplifying assumptions are made. First, it
is assumed that at the time the constraint applies, none of the
previous operations on the stream S in question have caused output
to go beyond the physical horizontal limits of the output device on
the output lines relevant to the constraints. I.e., it is assumed
that truncation and or wraparound of the output has not occurred on
these lines. Second, it is assumed that all of the characters
output to the stream on the output lines relevant to the constraints
are standard characters as defined in CLTL pp 20-21. The
non-standard character #\newline may have been used to end one line
and start the next. (Note that standard characters are all simple
characters such as A-Z. Particularly, #\tab, #\backspace,
#\newline, are NOT standard characters.) It is further assumed that
the strings (X and Y) referred to in the constraints consist solely
of standard characters.
Basic properties of LINE-POSITION:
1- For all S, (not (minusp (line-position S)).
2- For all S, (zerop (line-position (progn (terpri S) S))).
3- For all S, If something is at line position N on one line and
something else is at line position N on another line, then the
two things are lined up vertically one under the other.
Defining property of WRITE-SPACE
4- For all N,S, let M = (+ (line-position S) N)
if M <= (line-width S), then
(= (line-position (progn (write-space N S) S)) M)
Defining property of PRINTED-WIDTH
5- For all X,S, let M = (+ (line-position S) (printed-width X))
if 0 <= M <= (line-width S), then
(= (line-position (progn (write-string X S) S)) M)
Basic property of LINE-WIDTH
6- For all N,S, let P = (line-position S)
If (+ P N) <= (line-width S) then
(write-space N S) is guaranteed to output space on the end of
the current line without any truncation of wraparound occurring.
7- For all X,S, let P = (line-position S)
If 0 <= (+ P (printed-width X)) <= (line-width S) then
(write-string X S) is guaranteed to output X on the end of
the current line without any truncation of wraparound occurring.
Additional properties of PRINTED-WIDTH
8- For all X,Y (= (printed-width (concatenate 'string X Y) S)
(+ (printed-width X S) (printed-width Y S)))
9- For all X,Z (= (printed-width X S)
(+ (printed-width X (write-string Z S))))
-Support for varying width fonts.
A key motivation behind the functions above is dealing with
arbitrary kinds of output devices and output streams that support
variable width fonts. To provide for this, the properties above
place no absolute constraints on the units used for the width
values. In fact, the units can vary from stream to stream. The
only thing that is required is that for a given stream, the units
must be a constant throughout the life of the stream, and the four
functions above must all operate in terms of the same units. The
units should be chosen to be small enough to represent the minimum
possible difference in the length of two strings and large enough
that it is possible to perform (write-space 1). (I.e., a single
pixel is a logical choice.)
If an output stream only supports a single fixed width font, then
the logical width unit to choose is the width of a single character.
Given this choice, the following is a minimal implementation of the
four functions that meets the requirements above.
LINE-WIDTH returns the maximum number of characters which can be
printed on a single line. LINE-POSITION returns the number of
characters output since the last #\newline (or since the creation of
the stream if no #\newlines have been output). (WRITE-SPACE N S)
outputs N #\space characters. Finally, (PRINTED-LENGTH X S) =
(length X).
-Support for non-standard characters and situations where line width
has been exceeded.
In the main, the properties above can be supported even if the line
width has been exceeded and even when non-standard charactres are
involved. However, characters such as #\tab and #\newline can make
it impossible to support properties 7 and 8. In addition, when the
line width is exceeded, property 3 may not hold. It is hoped that
implementors will make a good faith effort to support the functions
in the full range of situations which can be encountered in their CL
implementations. However, the simple implementation suggested above
will probably provide at least 80% of the benefits intended. As a
result, it is important that people not allow the potential
difficulties of a full implementation deter them from making a
minimal implementation.
-Support for derivative streams.
Intentionally, very little is said about what the width units should
be or exactly what LINE-WIDTH should return. The only key criterion
is that LINE-WIDTH should return a result that is pessimistic enough
to ensure proper printing. However, it is useful to make some
comments about these matters with regard to certain types of
derivative streams.
If a synonym stream, two way stream, or echo stream is created, it should
have the same line-width and width unit as the base output stream.
A string output stream should have a line-width of NIL and probably
should be treated as supporting a fixed width font and having an
output width unit so that each character has a printed-width of 1.
If a broadcast stream is created, then LINE-LENGTH, LINE-POSITION,
and PRINTED-WIDTH should be be supported by reflecting them through
to the FIRST base stream. (There is no guarantee that anything
reasonable can be done with the streams as a set. For example, one
might support a varying length font while the others don't.) An
attempt should be made to send WRITE-SPACE requests to all of the
base streams. However, they may not come out right on other than
the first base stream.
Test Case:
Suppose that S is an output stream that supports a single fixed
width font which can display 72 characters on a line and that the
associated width unit is the width of one character. Evaluating the
following will produce the results shown.
(line-width S) => 72
(terpri S) => nil
(output-position S) => 0
(printed-width "testing: " S) => 9
(write-string "testing: " S) => "testing: "
(line-position S) => 9
(write-string "foo" S) => "foo"
(terpri S) => nil
(write-space 9 S) => T
(write-string "bar" S) => "bar"
The output produced is
testing: foo
bar
Rationale:
Pretty printing requires the function LINE-WIDTH to know how wide the
output it produces can be. Pretty printing requires LINE-POSITION to
determine where on the line output is when pretty printing starts.
Pretty printing requires PRINTED-WIDTH to determine how much space
things will take in the output. (If a variable width font is being
used, this cannot be determined without a detailed knowledge of the
font being used.) (Properties 7 & 8 greatly reduce the number of
times PRINTED-WIDTH has to be called.) Pretty printing requires
WRITE-SPACE to get proper indentations. (If a variable width font is
being used, indentations may be required that cannot be obtained by
outputting spaces.)
Current Practice:
Essentially every implementation of Common Lisp must support the
minimal functionality above internally in order to support PPRINT and
the FORMAT directives ~T and ~<...~>. However, there is no documented
interface to this functionality in CLTL. As a result, while some
implementations of Common Lisp make this functionality available to
users, some do not. Further, the implementations that do provide
this functionality do so in a variety of incompatible ways.
Cost to Implementors:
This proposal is written in such a way as to allow implementations
which do not have the ability to compute difficult values to just
return NIL. Very little work is forced. The idea is to offer
implementors a common way to provide this useful information to
portable programs where possible.
Cost to Users:
None. This change is upward compatible.
Cost of Non-Adoption:
Complex output programs such as pretty printers cannot be written portably.
Benefits:
A wide range of programs can gain better control of the format of output.
Aesthetics:
No significant aesthetic impact other than a slight increase in the
number of functions defined.
Discussion:
Dick Waters submitted a request for changes along the line of the
horizontal aspects of these functions in a letter to X3J13 dated
June 14, 1988. Pitman and Waters wrote up the request formally.
STREAM-INFO:ONE-DIMENSIONAL-FUNCTIONS is the minimum which is
required to support pretty printing into a stream which
displays output using a variable width font.
We drafted an alternate proposal, STREAM-INFO:TWO-DIMENSIONAL-FUNCTIONS,
which goes significantly beyond what is needed merely for pretty printing
and provides primitives LINE-DIMENSIONS, LINE-POSITION,
PRINTED-DIMENSIONS, and WRITE-SPACE but it is not included here.
A key point of contention which would be likely to swamp the 2d proposal
is the age old question of how to handle the issue of vertical distance
(where is the origin, which way do you count, ...). If anyone would
prefer to see larger problem 2d proposal, it could be circulated, but at
the last minute Pitman got worried that even the 1d version was going to
be controversial enough and decided to keep things focused on that.
For his own needs, Waters is strongly interested in having either
ONE-DIMENSIONAL-FUNCTIONS or TWO-DIMENSIONAL-FUNCTIONS proposal accepted,
but does not care which. Pitman concurs.
One variation of the 1d proposal might be useful to consider:
PRINTED-WIDTH could return two additional values: the number of newlines
that WRITE-STRING of the string would execute and the maximum X position
encountered (which might differ from the first value if the number of
newlines was non-zero).
This feature wasn't necessary for Waters' minimalist proposal, but Pitman
would be willing to write it in here if people thought it would be useful
enough for other purposes.
The 5th version was changed from the 4th by responding to suggestions
about better names for the functions, including a discussion of how
line-width should apply to various kinds of derivative streams, and
most importantly, by including a much more precise specification for
what the minimal capabilities of the functions should be.