/*
* This file is part of the X10 project (http://x10-lang.org).
*
* This file is licensed to You under the Eclipse Public License (EPL);
* You may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.opensource.org/licenses/eclipse-1.0.php
*
* This file was originally derived from the Polyglot extensible compiler framework.
*
* (C) Copyright 2000-2007 Polyglot project group, Cornell University
* (C) Copyright IBM Corporation 2007-2012.
*/
package polyglot.util;
import x10.util.CollectionFactory;
import java.io.PrintWriter;
import java.io.Writer;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
/**
* The pretty-printing algorithm is loosely based on the Modula-3
* pretty-printer, and on notes by Greg Nelson. It was extended to support
* breaks at multiple levels.
*
* OptimalCodeWriter follows the "break from root" rule: if a break is broken,
* breaks of equal or lower level in all containing blocks must also be
* broken, and breaks of strictly lower level in the same block must also
* be broken.
*
*/
public class OptimalCodeWriter extends CodeWriter {
/**
* Create a OptimalCodeWriter object with output stream <code>o</code>
* and width <code>width_</code>.
* @param o the writer to write to. Must be non-null.
* @param width_ the formatting width. Must be positive.
*/
public OptimalCodeWriter(OutputStream o, int width_) {
this(new PrintWriter(new OutputStreamWriter(o)), width_);
}
/**
* Create a OptimalCodeWriter object.
* @param o the writer to write to. Must be non-null.
* @param width_ the formatting width. Must be positive.
*/
public OptimalCodeWriter(PrintWriter o, int width_) {
output = o;
width = width_;
current = input = new BlockItem(null, 0);
if (OptimalCodeWriter.showInput) {
trace("new OptimalCodeWriter: width = " + width);
}
}
/**
* Create a OptimalCodeWriter object.
* @param o the writer to write to. Must be non-null.
* @param width_ the formatting width. Must be positive.
*/
public OptimalCodeWriter(Writer o, int width_) {
this(new PrintWriter(o), width_);
}
public void write(String s) {
if (s.length() > 0) write(s, s.length());
}
public void write(String s, int length) {
if (OptimalCodeWriter.showInput) {
trace("write '" + s + "' (" + length + ")");
}
current.add(new TextItem(s, length));
}
/**
* Start a new block with a relative indentation of <code>n</code>
* characters.
* <p>
* A block is a formatting unit. The formatting algorithm will try to put
* the whole block in one line unless
* <ul>
* <li>there is a level-0 break in the block, or</li>
* <li>the block cannot fit in one line.</li>
* </ul>
* <p>
* If either of the two conditions is satisfied, the formatting algorithm
* will break the block into lines by generating newlines for some of the
* inserted breaks. The first line is printed at the current cursor
* position <code>pos</code>, all the following lines are printed at the
* position <code>pos+n</code>.
*
* @param n
* the number of characters increased on indentation (relative
* to the current position) for all lines in the block.
* Requires: n >= 0.
*/
public void begin(int n) {
if (OptimalCodeWriter.showInput) {
trace("begin " + n);
incIndent();
}
BlockItem b = new BlockItem(current, n);
current.add(b);
current = b;
}
/**
* Terminate the most recent outstanding <code>begin</code>.
*/
public void end() {
if (OptimalCodeWriter.showInput) {
decIndent();
trace("end");
}
current = current.parent;
//@ assert current != null
// if (current == null) throw new RuntimeException();
}
public void allowBreak(int n, int level, String alt, int altlen) {
if (OptimalCodeWriter.showInput) {
trace("allowBreak " + n + " level=" + level);
}
current.add(new AllowBreak(n, level, alt, altlen, false));
}
/** @see polyglot.util.CodeWriter#unifiedBreak */
public void unifiedBreak(int n, int level, String alt, int altlen) {
if (OptimalCodeWriter.showInput) {
trace("unifiedBreak " + n + " level=" + level);
}
current.add(new AllowBreak(n, level, alt, altlen, true));
}
/**
* This method should be used sparingly; usually a call to
* <code>allowBreak</code> is preferable because forcing a newline also
* causes all breaks in containing blocks to be broken.
*/
public void newline() {
newline(0, 1);
}
/**
* Like newline(), but forces a newline with a specified indentation.
*/
public void newline(int n, int level) {
if (OptimalCodeWriter.showInput) {
trace("newline " + n);
}
current.add(new Newline(n, level));
}
/**
* Send out the current batch of text to be formatted. All outstanding
* <code>begin</code>'s are closed and the current indentation level is
* reset to 0. Returns true if formatting was completely successful (the
* margins were obeyed).
*/
public boolean flush() throws IOException {
return flush(true);
}
/** Like <code>flush</code>, but passing <code>format=false</code>
* causes output to be generated in the fastest way possible, with
* all breaks broken.
* @param format whether to pretty-print the output
* @return whether formatting was completely successful.
* @throws IOException
*/
public boolean flush(boolean format) throws IOException {
if (OptimalCodeWriter.showInput) {
trace("flush");
}
boolean success = true;
format_calls = 0;
if (format) {
try {
top = input;
Item.format(input, 0, 0, width, width,
new MaxLevels(Integer.MAX_VALUE, Integer.MAX_VALUE), 0, 0);
} catch (Overrun o) { success = false; }
} else success = false;
input.sendOutput(output, 0, 0, success, null);
output.flush();
if (OptimalCodeWriter.debug) {
System.err.println("Total calls to format = " + format_calls);
System.err.flush();
}
current = input = new BlockItem(null, 0);
return success;
}
public void close() throws IOException {
flush();
output.close();
}
/**
* Return a readable representation of all the structured input given to
* the CodeWriter since the last flush.
*/
public String toString() {
return input.toString();
}
protected BlockItem input;
protected BlockItem current;
protected static Item top;
protected PrintWriter output;
protected int width;
protected static int format_calls = 0;
public static final boolean debug = false; // show every step
public static final boolean showInput = false; // show input
public static final boolean visualize = false; // visualize formatting
// (requires VT100 terminal)
public static final boolean precompute = true; // use memoization
// Debugging methods
/** Amount to indent during tracing. */
protected int trace_indent = 0;
/** Increment tracing indentation. */
void incIndent() { trace_indent++; }
/** Decrement tracing indentation. */
void decIndent() {
trace_indent--;
if (trace_indent < 0) throw new RuntimeException("unmatched end");
}
/** Print a debug message. */
void trace(String s) {
for (int i = 0; i < trace_indent; i++) System.out.print(" ");
System.out.println(s);
}
}
/**
* An <code>Overrun</code> represents a formatting that failed because the
* right margin was exceeded by at least <code>amount</code> chars. If
* sameLine, the overrun occurred on the first line of the requested
* formatting; otherwise, it occurred on a subsequent line.
*/
class Overrun extends Exception
{
private static final long serialVersionUID = 2289236293356200970L;
int amount;
int type;
final static int POS = 0;
final static int WIDTH = 1;
final static int FIN = 2;
private static final Overrun overrun = new Overrun();
private Overrun() {}
static Overrun overrun(Item it, MaxLevels m, int amount, int type) {
if (OptimalCodeWriter.debug) System.err.println("-- Overrun: " + amount);
if (OptimalCodeWriter.visualize) {
System.err.print("\033[H\033[2J");
PrintWriter w = new PrintWriter(new OutputStreamWriter(System.err));
try {
OptimalCodeWriter.top.sendOutput(w, 0, 0, true, it);
}
catch (IOException e) { }
w.flush();
System.err.println();
String type_name;
switch (type) {
default:
case POS: type_name = "pos"; break;
case WIDTH: type_name = "width"; break;
case FIN: type_name = "fin"; break;
}
System.err.println(" overrun: type " + type_name + " amount: " + amount);
System.err.println(" next item is " + it);
System.err.println(" minPosWidth" + m + " of next item = " +
Item.getMinPosWidth(it, m));
System.err.println(" minWidth" + m + " of next item = " +
Item.getMinWidth(it, m));
System.err.println(" minIndent" + m + " of next item = " +
Item.getMinIndent(it, m));
System.err.println(" containsBreaks" + m + " of next item = " +
Item.containsBreaks(it, m));
try { System.in.read(); } catch (IOException e) {}
}
overrun.amount = amount;
overrun.type = type;
return overrun;
}
}
/**
* An <code>Item</code> is a piece of input handed to the formatter. It
* contains a reference to a possibly empty list of items that follow it.
*/
abstract class Item
{
/** next is null if this is the last item in the list. */
Item next;
protected Item() { next = null; }
/**
* Try to format this and subsequent items.
*
* @return the final cursor position (which may overrun rmargin, fin, or
* both), and set any contained breaks accordingly.
* </p>
*
* @param lmargin
* is the current left margin.
* @param pos
* is the current cursor position.
* @param rmargin
* is the current right margin.
* @param fin
* is a bound on the final cursor position after formatting the
* whole item.
* @param maxLevel
* is the maximum level at which breaks may be broken in the
* current block.
* @param maxLevelInner
* is the maximum level at which breaks may be broken in nested
* blocks.
* @param minLevel
* is the minimum level at which breaks must be broken.
* @param minLevelUnified
* is the minimum level at which unified breaks must be broken.
*
* <p>
* Breaks may be broken up to level maxLevel, which is set whenever a break
* is not broken. Not breaking an ordinary break means that equal or
* higher-level breaks in all contained blocks must not be broken either,
* and breaks of strictly higher level in the same block must not be
* broken. Not breaking a unified break means that breaks of the same
* level in the same block must not also be broken. The parameter
* maxLevelInner controls the maxLevel in nested blocks; it is equal to
* either maxLevel or maxLevel-1.
*
* <p>
* <dl>
* <dt>Example 1:
* <dd>Suppose we have a current maxLevel of 4, and an ordinary,
* non-unified break of level 2 is not broken. Then for that block,
* maxLevel is set to 2 and maxLevelInner is set to 1. This permits further
* breaks of level 1 or 2 in the same block, but only level-1 breaks in
* inner blocks.
*
* <dt>Example 2:</dt>
* <dd>Suppose we have a current maxLevel of 4, and a unified break of
* level 2 is not broken. Then for that block, maxLevel and maxLevelInner
* are set to 1. This permits no breaks in this block or in any nested
* blocks.</dd>
* </dl>
*
* <p>
* When a break is broken in a nested block, it means that all equal or
* higher-level breaks in containing blocks must be broken. However, these
* breaks may be encountered after the nested block. The parameter
* <code>minLevel</code> is used to communicate the level of breaks broken
* in nested blocks (and earlier in the current block). Any break of level
* <= minLevel <em>must</em> be broken. The parameter <code>
* minLevelUnified</code> is the minimum level at which unified breaks must
* be broken. minLevelUnified is equal to either minLevel or minLevel+1.
* </p>
*
* <dl>
* <dt>Example 3:
* <dd>Suppose we have a current maxLevel of 4, and a break of level 2 is
* broken. Then for its block, minLevel is at least 1, and minLevelUnified
* is at least 2. For containing blocks, minLevel is at least 2.</dd>
* </dl>
*
* <b>Note: </b> It is important that formatN not necessarily convert
* overruns in its final position into exceptions. This allows the calling
* routine to distinguish between 'internal' overruns and ones that it can
* tack on a conservative estimate of how much formatting the rest of the
* list will make the overrun go up by. Also, it simplifies the coding of
* formatN.
* </p>
*
* Requires: rmargin < lmargin, pos <= rmargin, lmargin < rmargin,
* pos ≤ rmargin, lmargin ≥ 0
*/
abstract FormatResult formatN(int lmargin, int pos, int rmargin, int fin,
MaxLevels m, int minLevel, int minLevelUnified) throws Overrun;
/**
* Send the output associated with this item to <code>o</code>, using the
* current break settings.
*
* @param success
*/
abstract int sendOutput(PrintWriter o, int lmargin, int pos, boolean success, Item last)
throws IOException;
// XXX
// the getminwidth etc. code is starting to duplicate the logic of the main
// formatting code. This suggests they should be merged. format can take
// two width arguments: one the width left on the current line, and one the
// width of subsequent lines. Hmmm -- new blocks start relative to current
// position, so knowing the initial width isn't enough.
/**
* Try to format a whole sequence of items in the manner of formatN. Unlike
* for formatN, The initial position may be an overrun (this is the only
* way that overruns are checked!). The item <code>it</code> may be also
* null, signifying an empty list. Requires: lmargin < rmargin, pos ≤
* rmargin, lmargin ≥ 0.
*
* @see formatN
*/
static FormatResult format(Item it,
int lmargin, int pos, int rmargin, int fin,
MaxLevels m, int minLevel, int minLevelUnified)
throws Overrun
{
OptimalCodeWriter.format_calls++;
if (OptimalCodeWriter.debug) {
if (it != null && it != OptimalCodeWriter.top) {
System.err.println("SNAPSHOT:");
PrintWriter w = new PrintWriter(new OutputStreamWriter(System.err));
try {
OptimalCodeWriter.top.sendOutput(w, 0, 0, true, it);
}
catch (IOException e) { }
w.write("<END>\n");
w.flush();
}
System.err.println("Format: " + it + "\n lmargin = " +
lmargin + " pos = " + pos + " fin = " + fin +
" max break levels: " + m +
" min break levels: " + minLevel + "/" + minLevelUnified);
if (OptimalCodeWriter.debug) {
System.err.println(" MinWidth = " + getMinWidth(it, m));
System.err.println(" MinPosWidth = " + getMinPosWidth(it, m));
System.err.println(" MinIndent = " + getMinIndent(it, m));
}
System.err.flush();
}
if (it == null) { // no items to format. Check against final position.
if (pos > fin) {
if (OptimalCodeWriter.debug)
System.err.println("Final position overrun: " + (pos-fin));
throw Overrun.overrun(it, m, pos - fin, Overrun.FIN);
}
else return new FormatResult(pos, minLevelUnified);
}
int amount2 = lmargin + getMinWidth(it, m) - rmargin;
// lmargin is too far right
if (amount2 > 0) {
if (OptimalCodeWriter.debug)
System.err.println("Width overrun: " + amount2);
throw Overrun.overrun(it, m, amount2, Overrun.WIDTH);
}
int amount = pos + getMinPosWidth(it, m) - rmargin; // overrun on first line
if (amount > 0) {
if (OptimalCodeWriter.debug)
System.err.println("Position (first line) overrun: " + amount);
throw Overrun.overrun(it, m, amount, Overrun.POS);
}
int amount3 = lmargin + getMinIndent(it, m) - fin; // overrun on last line
if (amount3 > 0) {
if (OptimalCodeWriter.debug)
System.err.println("Final position (predicted) overrun: " + amount3);
throw Overrun.overrun(it, m, amount3, Overrun.FIN);
}
return it.formatN(lmargin, pos, rmargin, fin, m,
minLevel, minLevelUnified);
}
/*
* The following fields keep track of the tightest formatting that is possible
* with an item and its following items, if all breaks are broken. The purpose
* is to more aggressively tighten bounds when an overrun occurs. Formatting is
* measured relative to both "lmargin" and to "pos". T
*
* lmargin pos
* | |
* | xxxxx
* xxxxxxxx
* xxxxxx
* <------> min_width (at least min_pos_width):
* distance from lmargin to rightmost char
* <---> min_pos_width: distance from initial pos to end of first line
* <----> min_indent (at most min_width):
* distance from lmargin to final position on last line
*/
public static final int NO_WIDTH = -9999;
public static final int NEWLINE_VIOLATION = 9999; // a big number XXX (hack)
/** Minimum lmargin-rhs width on second and following lines.
* A map from max levels to Integer(width). */
Map<MaxLevels, Integer> min_widths = CollectionFactory.newHashMap();
/** Minimum lmargin-final offset */
Map<MaxLevels, Integer> min_indents = CollectionFactory.newHashMap();
/** Minimum pos-rhs width (i.e., min width up to first break) */
Map<MaxLevels, Integer> min_pos_width = CollectionFactory.newHashMap();
static int getMinWidth(Item it, MaxLevels m) {
if (it == null) return NO_WIDTH;
if (it.min_widths.containsKey(m))
return ((Integer)it.min_widths.get(m)).intValue();
int p1 = it.selfMinWidth(m);
int p2 = it.selfMinIndent(m);
int p3 = (p2 != NO_WIDTH) ? getMinPosWidth(it.next, m) + p2 : NO_WIDTH;
int p4 = getMinWidth(it.next, m);
if (OptimalCodeWriter.debug)
System.err.println("minwidth" + m + ": item = " + it + ": p1 = " + p1 + ", p2 = " + p2 + ", p3 = " + p3 + ", p4 = " + p4);
int result = Math.max(Math.max(p1, p3), p4);
it.min_widths.put(m, new Integer(result));
return result;
}
static int getMinPosWidth(Item it, MaxLevels m) {
if (it == null) return 0;
if (it.min_pos_width.containsKey(m)) {
return ((Integer)it.min_pos_width.get(m)).intValue();
}
int p1 = it.selfMinPosWidth(m);
int result;
if (it.next == null || it.selfContainsBreaks(m)) {
result = p1;
if (OptimalCodeWriter.debug)
System.err.println("minpos " + m + ": item = " + it + ": p1 = " + p1);
} else {
result = p1 + getMinPosWidth(it.next, m);
if (OptimalCodeWriter.debug)
System.err.println("minpos " + m + ": item = " + it + ": p1 = " + p1 + " + " + getMinPosWidth(it.next, m) + " = " + result);
}
it.min_pos_width.put(m, new Integer(result));
return result;
}
static int getMinIndent(Item it, MaxLevels m) {
if (it == null) return NO_WIDTH;
if (it.min_indents.containsKey(m)) {
return ((Integer)it.min_indents.get(m)).intValue();
}
int p1 = it.selfMinIndent(m);
if (it.next == null) return p1;
int result;
if (containsBreaks(it.next, m))
result = getMinIndent(it.next, m);
else
result = getMinPosWidth(it.next, m);
it.min_indents.put(m, new Integer(result));
return result;
}
static boolean containsBreaks(Item it, MaxLevels m) {
if (it == null) return false;
if (it.selfContainsBreaks(m)) {
if (OptimalCodeWriter.debug)
System.err.println("containsBreaks " + m + " of " + it + ": true");
return true;
}
if (it.next == null) {
if (OptimalCodeWriter.debug)
System.err.println("containsBreaks " + m + " of " + it + ": false");
return false;
}
return containsBreaks(it.next, m);
}
public String summarize(String s) {
return s;
// if (s.length() <= 79) return s;
// return s.substring(0, 76) + "...";
}
public String toString() {
if (next == null) return summarize(selfToString());
return summarize(selfToString() + next.toString());
}
abstract String selfToString();
abstract int selfMinIndent(MaxLevels m);
abstract int selfMinWidth(MaxLevels m);
abstract int selfMinPosWidth(MaxLevels m);
abstract boolean selfContainsBreaks(MaxLevels m);
}
/** A simple string. */
class TextItem extends Item {
String s; //@ invariant s != null
int length;
TextItem(String s_, int length_) { s = s_; length = length_; }
FormatResult formatN(int lmargin, int pos, int rmargin, int fin,
MaxLevels m, int minLevel, int minLevelUnified) throws Overrun {
return format(next, lmargin, pos + length, rmargin, fin,
m, minLevel, minLevelUnified);
// all overruns passed through
}
int sendOutput(PrintWriter o, int lm, int pos, boolean success, Item last) throws IOException {
o.write(s);
return pos + length;
}
boolean selfContainsBreaks(MaxLevels m) { return false; }
int selfMinIndent(MaxLevels m) { return NO_WIDTH; }
int selfMinWidth(MaxLevels m) { return NO_WIDTH; } // length only counts on s lines
int selfMinPosWidth(MaxLevels m) { return length; }
String selfToString() {
java.io.StringWriter sw = new java.io.StringWriter();
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
if (c == ' ') sw.write("\\ ");
else sw.write(c);
}
return sw.toString();
}
/**
* @param item
*/
public void appendTextItem(TextItem item) {
s += item.s;
length += item.length;
}
}
class AllowBreak extends Item {
final int indent;
final int level;
final boolean unified;
final String alt;
final int altlen;
boolean broken = false;
//@ invariant indent >= 0
//@ invariant alt != null
//@ requires n_ >= 0
//@ requires alt_ != null
AllowBreak(int n_, int level_, String alt_, int altlen_, boolean u) {
indent = n_; alt = alt_; altlen = altlen_;
level = level_; unified = u; }
FormatResult formatN(int lmargin, int pos, int rmargin, int fin,
MaxLevels m, int minLevel, int minLevelUnified) throws Overrun {
if (canLeaveUnbroken(minLevel, minLevelUnified)) {
// first, we can try not breaking it
try {
if (OptimalCodeWriter.debug)
System.err.println("trying not breaking it.");
broken = false;
return format(next, lmargin, pos + altlen, rmargin, fin,
new MaxLevels(
Math.min(unified ? level - 1 : level, m.maxLevel),
Math.min(level - 1, m.maxLevelInner)),
minLevel, minLevelUnified);
}
// |yyy^xxxx
// |xxxxx
// |xxx
// pos overrun: might help by breaking
// width overrun: might help by breaking (e.g., if breaking permits nested breaks)
// fin overrun: similar
catch (Overrun o) {
if (OptimalCodeWriter.debug) {
System.err.println("not breaking caused overrun of " +
o.amount);
}
if (level > m.maxLevel) {
if (OptimalCodeWriter.debug) {
System.err.println("not breaking failed, " +
"but can't break either.");
}
throw o; // can't break it
}
}
}
if (canBreak(m)) { // now, we can try breaking it
if (OptimalCodeWriter.debug)
System.err.println("trying breaking at " + this);
broken = true;
try {
return format(next, lmargin, lmargin + indent, rmargin, fin, m,
Math.max(level-1, minLevel),
Math.max(level, minLevelUnified));
}
// |yyy^
// | xxxx
// | xxxxxx
// | xxx
// pos overrun: becomes a width overrun?
// width overrun: remains
// fin overrun: remains? becomes a width overrun?
catch (Overrun o) {
o.type = Overrun.WIDTH; throw o;
}
}
throw new IllegalArgumentException(
"internal error: could not either break or not break");
}
int sendOutput(PrintWriter o, int lmargin, int pos, boolean success,
Item last)
throws IOException
{
if (broken || !success) {
o.println();
for (int i = 0; i < lmargin + indent; i++) o.print(" ");
//o.write("(" + (lmargin+indent) + ")");
return lmargin + indent;
} else {
o.print(alt);
return pos + altlen;
}
}
boolean canBreak(MaxLevels m) {
return level <= m.maxLevel;
}
boolean canLeaveUnbroken(int minLevel, int minLevelUnified) {
return (level > minLevelUnified || !unified && level > minLevel);
}
int selfMinIndent(MaxLevels m) {
if (canBreak(m)) return indent;
else return NO_WIDTH;
}
int selfMinPosWidth(MaxLevels m) {
if (canBreak(m)) return 0;
else return altlen;
}
int selfMinWidth(MaxLevels m) {
if (canBreak(m)) return indent;
else return NO_WIDTH;
}
boolean selfContainsBreaks(MaxLevels m) {
return canBreak(m);
}
String selfToString() {
if (indent == 0) return " ";
else return "^" + indent; }
}
/** A Newline is simply a level-1 break that cannot be
* left unbroken.
*/
class Newline extends AllowBreak {
Newline(int n) {
this(n, 1);
}
Newline(int n, int level) {
super(n, level, "\n", 0, true);
broken = true;
}
boolean canLeaveUnbroken() { return false; }
String selfToString() {
if (indent == 0) return "\\n";
else return "\\n[" + indent + "]"; }
// XXX should not need to override sendOutput
int sendOutput(PrintWriter o, int lmargin, int pos, boolean success, Item last)
throws IOException {
broken = true; // XXX how can this be necessary?
return super.sendOutput(o, lmargin, pos, success, last);
}
int selfMinIndent(MaxLevels m) {
if (canBreak(m)) return indent;
else return NEWLINE_VIOLATION;
}
int selfMinPosWidth(MaxLevels m) {
if (canBreak(m)) return 0;
else return NEWLINE_VIOLATION;
}
int selfMinWidth(MaxLevels m) {
if (canBreak(m)) return indent;
else return NEWLINE_VIOLATION;
}
}
/**
* A BlockItem is a formatting unit containing a list of other items to be
* formatted.
*/
class BlockItem extends Item {
BlockItem parent;
Item first;
Item last;
int indent; //@ invariant indent >= 0
BlockItem(BlockItem parent_, int indent_) {
parent = parent_;
first = last = null;
indent = indent_;
}
/**
* Add a new item to the end of the block. Successive StringItems are
* concatenated together to limit recursion depth when formatting.
*/
void add(Item it) {
if (first == null) {
first = it;
} else {
if (it instanceof TextItem && last instanceof TextItem) {
TextItem lasts = (TextItem)last;
lasts.appendTextItem(((TextItem)it));
return;
} else {
last.next = it;
}
}
last = it;
}
FormatResult formatN(int lmargin, int pos, int rmargin, int fin,
MaxLevels m, int minLevel, int minLevelUnified) throws Overrun {
int childfin = fin;
if (childfin + getMinPosWidth(next, m) > rmargin) {
childfin = rmargin - getMinPosWidth(next, m);
}
while (true) {
FormatResult fr = format(first, pos + indent, pos, rmargin, childfin,
new MaxLevels(m.maxLevelInner, m.maxLevelInner), 0, 0);
int minLevel2 = Math.max(minLevel, fr.minLevel);
int minLevelU2 = Math.max(minLevelUnified, fr.minLevel);
try {
return format(next, lmargin, fr.pos, rmargin, fin,
m, minLevel2, minLevelU2);
} catch (Overrun o) {
if (o.type == Overrun.WIDTH) {
o.type = Overrun.FIN;
// Idea: doesn't matter where next item started XXX really?
throw o;
}
childfin -= o.amount;
}
}
}
int sendOutput(PrintWriter o, int lmargin, int pos, boolean success, Item last) throws IOException {
Item it = first;
lmargin = pos + indent;
if (last != this) {
while (it != null) {
pos = it.sendOutput(o, lmargin, pos, success, last);
if (last != null && it == last) {
throw new IOException();
}
it = it.next;
}
} else {
o.print("...");
}
return pos;
}
int selfMinWidth(MaxLevels m) {
return getMinWidth(first,
new MaxLevels(m.maxLevelInner, m.maxLevelInner));
}
int selfMinPosWidth(MaxLevels m) {
return getMinPosWidth(first,
new MaxLevels(m.maxLevelInner, m.maxLevelInner));
}
int selfMinIndent(MaxLevels m) {
return getMinIndent(first,
new MaxLevels(m.maxLevelInner, m.maxLevelInner));
}
/**
* Map from maxlevels to either null or non-null, the latter if it can
* contain breaks at those maxlevels.
*/
Map<MaxLevels, MaxLevels> containsBreaks = CollectionFactory.newHashMap();
boolean selfContainsBreaks(MaxLevels m) {
if (containsBreaks.containsKey(m)) {
return (containsBreaks.get(m) != null);
}
boolean result =
containsBreaks(first,
new MaxLevels(m.maxLevelInner, m.maxLevelInner));
containsBreaks.put(m, result ? m : null);
return result;
}
String selfToString() {
if (indent == 0) return "[" + first + "]";
else return "[" + indent + first + "]";
}
}
class FormatResult {
int pos;
int minLevel;
FormatResult(int pos_, int minLevel_) {
pos = pos_;
minLevel = minLevel_;
}
}
class MaxLevels {
int maxLevel;
int maxLevelInner;
MaxLevels(int ml, int mli) {
maxLevel = ml;
maxLevelInner = mli;
}
public int hashCode() {
return maxLevel * 17 + maxLevelInner;
}
public boolean equals(Object o) {
if (o instanceof MaxLevels) {
MaxLevels m2 = (MaxLevels)o;
return (maxLevel == m2.maxLevel &&
maxLevelInner == m2.maxLevelInner);
} else
return false;
}
public String toString() {
return "[" + maxLevel + "/" + maxLevelInner + "]";
}
}