Writes tokens to an underlying writer
, respecting certain formatting settings and a maximum line width.
The FormattingWriter
manages the following aspects:
Additionally, it also writes comments if a token stream is given.
Two indentation levels are associated with each token: one before the token, and one after it. Each token also introduces a context which tracks these indentation levels if they stack. In this case, their indentation is applied to all subsequent lines until the context is closed.
By default, the indentation before the token never
stacks,
and the indentation after the token always
does.
However, either indentation may also stack in the other case,
or only if it is actually applied, that is,
only if that token is the first/last of its line.
When the token is written, its context (instance of FormattingContext
)
is pushed onto a context stack.
The indentation of each line is then the sum of all indentation currently on the stack,
plus the indentation before the current token and after the last token (if they’re not already on the stack).
When a context is closed, the context and all contexts on top of it are removed from the context stack.
A context can be closed in two ways:
}
closes the context of the corresponding opening brace {
:
The block has ended, and subsequent lines should no longer be indented as if they were still part of the block.
Tokens that close another token’s context may not introduce indentation themselves,
since they don’t get a context of their own.closeContext()
.You can also obtain a context not associated with any token by calling openContext()
,
which introduces some undirectional indentation that always stacks.
This is mostly useful if you have a closing token with no designated opening token:
for example, a statement’s closing semicolon ;
should close some context,
but there is no corresponding token which opens that context.
Examples:
{
has an indentAfter
of 1, which always stacks.
The resulting context is closed by the associated closing brace }
..
has an indentBefore
of 1, which never stacks.
If it stacked, you would get this:value someValue = something .foo(thing) .bar(otherThing) .baz(whyIsThisSoIndented);
=>
has an indentBefore
of 2, which stacks only if it is applied.
Thus, you get both of the following results:Integer i => function( longArgument // only indented by one level, from the ( ); Integer f(String param1, String param2) => let (thing = param1.length, thing2 = param2.uppercased) thing + thing2.string; // indented from both => and let
Two Integer
ranges are associated with each token. One indicates how many line breaks
may occur before the token, and the other indicates how many may occur after the token. Additionally,
one may call requireAtLeastLineBreaks()
to further restrict how many line breaks may occur between
two tokens.
The intersection of these ranges for the border between two tokens is then used to determine how many line breaks should be written before the token.
tokens
exists, then each time a token is written, the token stream is fast-forwarded until
the token is met (if a token with a different text is met, an exception is thrown). In
fast-forwarding, the amount of line breaks is counted. After fast-forwarding has finished, the
number of line breaks that were counted is clamped into the line break range, and this many
line breaks are written.tokens
doesn’t exist, then the first element of the range is used (usually the lowest,
unless the range is decreasing).(Internally, the FormattingWriter
also keeps track if a line break range came from writeToken()
,
requireAtLeastLineBreaks()
, or was added internally when dealing with comments; an empty
intersection of two ranges is usually a bug, unless one of the ranges comes from a comment,
in which case we just use that range instead.)
Additionally, the FormattingWriter
also breaks lines according to a maximum line length and
a LineBreakStrategy
, as determined by options
.
To achieve this, tokens are not directly written to the underlying writer; instead, they are
added to a token queue (not to be confused with the token stack, which is used for indentation).
Each time a token is added, the FormattingWriter
checks if there are enough tokens on the queue
for the line break strategy to decide where a line break should be placed. Line breaks are allowed
between tokens if their respecive ranges included at least one value greater than zero (in other
words, to disallow a line breaks between two tokens, pass a range of 0..0
to either of them).
When a line break location is known, that line is written and its tokens removed from the queue
(their contexts are then added to the token stack).
If you’ve made it this far, relax, this is the easiest section :)
Two Integer
s are associated with each token. One indicates the token’s desire to have a space
before it, the other indicates the desire to have a space after it. When the two tokens are written,
these integers are added, and if the sum is >= 0
, then a space is written.
To avoid inverting the intended result by numerical overflow, don’t use values outside the range
minDesire
..
maxDesire
. You can also give false
and true
to writeToken()
, which
are convenient and readable syntax sugar for these two values (spaceBefore = true
).
The fast-forwarding of the token stream (if given) was already mentioned in the “Line Breaking” section. If comment tokens are encountered during the fast-forwarding, they are written out like tokens with
spaceBefore = maxDesire - 1, spaceAfter = maxDesire - 1
indentBefore = Indent(0), indentAfter = Indent(0)
lineBreaksBefore, lineBreaksAfter
as determined by the options
// line comments
/* single-line multi comments */
/* multi-line comments */
Anything
FormattingWriter
Basic
Identifiable
Object
Anything
Destroyable
Usable
no subtypes hierarchy
Initializer |
FormattingWriter(TokenStream? tokens, Writer writer, FormattingOptions options) |
Attributes | |
tokens | Source Codeshared TokenStream? tokens |
Inherited Attributes |
Attributes inherited from: Object hash , string |
Methods | |
closeContext | Source Codeshared void closeContext(FormattingContext context) Close a |
destroy | Source Codeshared actual void destroy(Throwable? error) Enqueue a line break if the last queue element isn’t a line break, then flush the queue. Refines Destroyable.destroy |
openContext | Source Codeshared FormattingContext openContext(Integer indentAfter = 0) Open a Parameters:
|
requireAtLeastLineBreaks | Source Codeshared void requireAtLeastLineBreaks(Integer limit, Boolean fastForwardFirst = true) Parameters:
|
requireAtMostLineBreaks | Source Codeshared void requireAtMostLineBreaks(Integer limit, Boolean fastForwardFirst = true) Parameters:
|
writeToken | Source Codeshared FormattingContext? writeToken(AntlrToken|String token, FormattingContext? context = null, Integer indentBefore = 0, Integer indentAfter = 0, StackCondition stackIndentBefore = ..., StackCondition stackIndentAfter = ..., Range<Integer> lineBreaksBefore = ..., Range<Integer> lineBreaksAfter = ..., Integer|Boolean spaceBefore = 0, Integer|Boolean spaceAfter = 0, AntlrToken|String tokenInStream = ...) Add a single token, then try to write out a line. See the class documentation for more information on the token model and how the various parameters of a token interact. All parameters (except Parameters:
|
Inherited Methods |
Methods inherited from: Object equals |
Methods inherited from: Destroyable destroy |
Nested Aliases | |
QueueElement | Source Codeshared QueueElement=> Token|Empty|LineBreak |
Nested Interfaces | |
ClosingElement | Source Codeshared ClosingElement |
Element | Source Codeshared Element |
FormattingContext | Source Codeshared FormattingContext |
OpeningElement | Source Codeshared OpeningElement |
Nested Classes | |
ClosingToken | Source Codeshared ClosingToken |
Empty | Source Codeshared abstract Empty |
InvariantToken | Source Codeshared InvariantToken |
LineBreak | Source Codeshared LineBreak |
OpeningToken | Source Codeshared OpeningToken |
Token | Source Codeshared abstract Token |