15 | /// Be careful not to duplicate this structure.
16 | ///
17 | /// When pretty-printing any language as text, it's a challenge to decide
18 | /// where to place newlines. You may want to break up long lines into
19 | /// shorter ones, as in
20 | ///
21 | /// if (ReallyLongIdentifier[Fully.Qualified.Name(multiple, parameters)]
22 | /// > SomeConstant)
23 | /// {
24 | /// return ReallyLongIdentifier[firstThing + secondThing]
25 | /// + thirdThing + fourthThing;
26 | /// }
27 | ///
28 | /// Conversely, you may want to print something on one line that you would
29 | /// ordinarily print on two:
30 | ///
31 | /// if (c) break;
32 | ///
33 | /// Of course, the problem is, you don't know how long the syntax tree
34 | /// will be in text form until after you try to print it.
35 | ///
36 | /// My first idea to solve this problem was to use a
37 | /// rope
38 | /// tree data structure - inner syntax trees would produce small strings
39 | /// that could be "roped" together to produce a bigger tree. But ropes tend
40 | /// not to use memory efficiently, and there was the challenge, which I
41 | /// didn't see how to solve, was how to keep the tree balanced efficiently
42 | /// (for this particular application perhaps a balanced tree wasn't needed,
43 | /// but as a perfectionist I didn't want to implement a "half-baked" data
44 | /// structure.)
45 | ///
46 | /// Next I thought of the solution used here, a simpler solution based on an
47 | /// ordinary StringBuilder. My idea was to insert newlines "pessimistically"
48 | /// - insert them everywhere in which they might be needed - and then
49 | /// selectively "revoke" them later if they turn out to be unnecessary. Only
50 | /// the most recently-written newline(s) can be revoked, which keeps the
51 | /// implementation simple and also limits the performance cost of deleting
52 | /// the newlines.
53 | ///
54 | /// To use, call Newline() to write a newline (with indentation). To make
55 | /// a decision about whether to keep or revoke the most recent newline(s),
56 | /// call RevokeOrCommitNewlines(cp, maxLineLength) where cp is a "checkpoint"
57 | /// representing some point before the first newline want to potentially
58 | /// revoke, and maxLineLength is the line length threshold: if the line length
59 | /// after combining lines starting at the line on which the checkpoint is
60 | /// located does not exceed maxLineLength, then the newlines are revoked,
61 | /// otherwise ALL newlines are committed (so earlier newlines can no longer
62 | /// be revoked.)
63 | ///
64 | /// This design allows a potentially long series of newlines to be deleted
65 | /// in the reverse order that they were created, but if any newline is kept
66 | /// then previous ones can no longer be deleted.
67 | ///
68 | public struct PrinterState
69 | {
70 | public StringBuilder S;
71 | public int IndentLevel;
72 | public int LineNo;
73 | public string IndentString;
74 | public string NewlineString;
75 | private int _lineStartIndex;
76 | public int LineStartIndex { get { return _lineStartIndex; } }
77 | public int IndexInCurrentLine { get { return S.Length - _lineStartIndex; } }
78 | private InternalList