package polyglot.util; 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; public class CodeWriter { public CodeWriter(OutputStream o, int width_) { this(new PrintWriter(new OutputStreamWriter(o)), width_); } public CodeWriter(PrintWriter o, int width_) { super(); output = o; width = width_; current = (input = new BlockItem(null, 0)); if (CodeWriter.showInput) { trace("new CodeWriter: width = " + width); } } public CodeWriter(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 (CodeWriter.showInput) { trace("write \'" + s + "\' (" + length + ")"); } current.add(new TextItem(s, length)); } public void begin(int n) { if (CodeWriter.showInput) { trace("begin " + n); incIndent(); } BlockItem b = new BlockItem(current, n); current.add(b); current = b; } public void end() { if (CodeWriter.showInput) { decIndent(); trace("end"); } current = current.parent; } public void allowBreak(int n) { allowBreak(n, 1, "", 1); } public void allowBreak(int n, String alt) { allowBreak(n, 1, alt, 1); } public void allowBreak(int n, int level, String alt, int altlen) { if (CodeWriter.showInput) { trace("allowBreak " + n + " level=" + level); } current.add(new AllowBreak(n, level, alt, altlen, false)); } public void unifiedBreak(int n, int level, String alt, int altlen) { if (CodeWriter.showInput) { trace("unifiedBreak " + n + " level=" + level); } current.add(new AllowBreak(n, level, alt, altlen, true)); } public void newline() { newline(0, 1); } public void newline(int n, int level) { if (CodeWriter.showInput) { trace("newline " + n); } current.add(new Newline(n, level)); } public void newline(int n) { newline(n, 1); } public boolean flush() throws IOException { return flush(true); } public boolean flush(boolean format) throws IOException { if (CodeWriter.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 (CodeWriter.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(); } 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; final public static boolean debug = false; final public static boolean showInput = false; final public static boolean visualize = false; final public static boolean precompute = true; protected int trace_indent = 0; void incIndent() { trace_indent++; } void decIndent() { trace_indent--; if (trace_indent < 0) throw new RuntimeException("unmatched end"); } void trace(String s) { for (int i = 0; i < trace_indent; i++) System.out.print(" "); System.out.println(s); } } class Overrun extends Exception { int amount; int type; final static int POS = 0; final static int WIDTH = 1; final static int FIN = 2; final private static Overrun overrun = new Overrun(); private Overrun() { super(); } static Overrun overrun(Item it, MaxLevels m, int amount, int type) { if (CodeWriter.debug) System.err.println("-- Overrun: " + amount); if (CodeWriter.visualize) { System.err.print("\033[H\033[2J"); PrintWriter w = new PrintWriter(new OutputStreamWriter(System.err)); try { CodeWriter.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; } } abstract class Item { Item next; protected Item() { super(); next = null; } abstract FormatResult formatN(int lmargin, int pos, int rmargin, int fin, MaxLevels m, int minLevel, int minLevelUnified) throws Overrun; abstract int sendOutput(PrintWriter o, int lmargin, int pos, boolean success, Item last) throws IOException; static FormatResult format(Item it, int lmargin, int pos, int rmargin, int fin, MaxLevels m, int minLevel, int minLevelUnified) throws Overrun { CodeWriter.format_calls++; if (CodeWriter.debug) { if (it != null && it != CodeWriter.top) { System.err.println("SNAPSHOT:"); PrintWriter w = new PrintWriter(new OutputStreamWriter(System.err)); try { CodeWriter.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 (CodeWriter.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) { if (pos > fin) { if (CodeWriter.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; if (amount2 > 0) { if (CodeWriter.debug) System.err.println("Width overrun: " + amount2); throw Overrun.overrun(it, m, amount2, Overrun.WIDTH); } int amount = pos + getMinPosWidth(it, m) - rmargin; if (amount > 0) { if (CodeWriter.debug) System.err.println("Position (first line) overrun: " + amount); throw Overrun.overrun(it, m, amount, Overrun.POS); } int amount3 = lmargin + getMinIndent(it, m) - fin; if (amount3 > 0) { if (CodeWriter.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); } final public static int NO_WIDTH = -9999; final public static int NEWLINE_VIOLATION = 9999; Map min_widths = new HashMap(); Map min_indents = new HashMap(); Map min_pos_width = new HashMap(); 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 (CodeWriter.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 (CodeWriter.debug) System.err.println("minpos " + m + ": item = " + it + ": p1 = " + p1); } else { result = p1 + getMinPosWidth(it.next, m); if (CodeWriter.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 (CodeWriter.debug) System.err.println("containsBreaks " + m + " of " + it + ": true"); return true; } if (it.next == null) { if (CodeWriter.debug) System.err.println("containsBreaks " + m + " of " + it + ": false"); return false; } return containsBreaks(it.next, m); } public String summarize(String 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); } class TextItem extends Item { String s; int length; TextItem(String s_, int length_) { super(); 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); } 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; } 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(); } 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; AllowBreak(int n_, int level_, String alt_, int altlen_, boolean u) { super(); 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)) { try { if (CodeWriter.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); } catch (Overrun o) { if (CodeWriter.debug) { System.err.println("not breaking caused overrun of " + o.amount); } if (level > m.maxLevel) { if (CodeWriter.debug) { System.err.println("not breaking failed, " + "but can\'t break either."); } throw o; } } } if (canBreak(m)) { if (CodeWriter.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)); } 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(" "); 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; } } 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 + "]"; } int sendOutput(PrintWriter o, int lmargin, int pos, boolean success, Item last) throws IOException { broken = true; 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; } } class BlockItem extends Item { BlockItem parent; Item first; Item last; int indent; BlockItem(BlockItem parent_, int indent_) { super(); parent = parent_; first = (last = null); indent = indent_; } 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; 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 containsBreaks = new HashMap(); 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_) { super(); pos = pos_; minLevel = minLevel_; } } class MaxLevels { int maxLevel; int maxLevelInner; MaxLevels(int ml, int mli) { super(); 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 + "]"; } }