001: package xtc.tree;
002:
003: import java.io.OutputStream;
004: import java.io.PrintWriter;
005: import java.io.Writer;
006:
007: import xtc.Constants;
008: import xtc.lang.JavaPrinter;
009:
010: /**
011: * A printer that guarantees that each Node is printed at the <em>exact</em>
012: * file, line, and column specified by its location. Where necessary, this
013: * printer prints line markers (similar to those used by the C preprocessor:<br>
014: *
015: * <tt>#</tt> <i>line</i> <tt>"</tt><i>file</i><tt>"</tt><br>
016: *
017: * Reproducing exact source locations helps downstream tools track symbolic
018: * information for error messages, debugging, and exception backtraces. Even
019: * though this printer makes a best effort to print nodes without locations
020: * nicely, experience shows that the generated code is usually harder to read
021: * for humans (less "pretty") than with the superclass xtc.tree.Printer.
022: */
023: public class LineupPrinter extends Printer {
024: /**
025: * Is the current line blank? If yes, the printer delays any indentation until
026: * it encounters the first non-blank character on the line.
027: */
028: protected boolean blankLine = true;
029:
030: /** Does the node currently being visited have a location? */
031: protected boolean currentNodeHasLocation = true;
032:
033: /** File name specified by the last node that had a location. */
034: protected String lastFile = null;
035:
036: /** Line number specified by the last node that had a location. */
037: protected long lastLine = 0;
038:
039: /** File name specified by the last emitted line marker. */
040: protected String markedFile = null;
041:
042: /** Line number specified by the last emitted line marker. */
043: protected long markedLine = 0;
044:
045: /** Line number in the actual output after the last emitted line marker. */
046: protected long markedPhysical = 0;
047:
048: protected boolean showFilePaths = true;
049:
050: public LineupPrinter(final OutputStream out,
051: final boolean showFilePaths) {
052: this (new PrintWriter(out, false), showFilePaths);
053: }
054:
055: public LineupPrinter(final PrintWriter out,
056: final boolean showFilePaths) {
057: super (out);
058: this .showFilePaths = showFilePaths;
059: }
060:
061: public LineupPrinter(final Writer out, final boolean showFilePaths) {
062: this (new PrintWriter(out, false), showFilePaths);
063: }
064:
065: public Printer align(final int desiredColumn) {
066: if (blankLine) {
067: indent = desiredColumn;
068: return this ;
069: }
070: return super .align(desiredColumn);
071: }
072:
073: /** Current line in the output adjusted for line markers. */
074: protected long effectiveLine() {
075: return markedLine + line - markedPhysical;
076: }
077:
078: /** If blankLine is true, print any deferred line breaks and blank characters. */
079: protected void endBlank() {
080: if (blankLine) {
081: if (Constants.FIRST_COLUMN < column) {
082: if (!currentNodeHasLocation && null != lastFile)
083: /* make it appear to debugger as if line+file stand still */
084: printLineMarker(lastFile, lastLine);
085: else
086: super .pln();
087: }
088: blankLine = false;
089: super .indent();
090: }
091: }
092:
093: /**
094: * If the given node has a location, ensure that the output file, line, and
095: * column is at exactly that location, by emitting some combination of line
096: * markers, line breaks, and blanks.
097: */
098: protected void ensureLocation(final Node n) {
099: if (null == n || !n.hasLocation())
100: return;
101: final Location nl = n.getLocation();
102: /* Note: columns in locations are 1-based, whereas printer columns are based
103: * on Constants.FIRST_COLUMN. Therefore, this code adjusts nl.column. */
104: final int nlColumn = nl.column + Constants.FIRST_COLUMN - 1;
105: if (null == markedFile || !markedFile.equals(nl.file)
106: || nl.line < effectiveLine()
107: || effectiveLine() + 2 < nl.line
108: || nl.line == effectiveLine() && nlColumn < column) {
109: markedFile = nl.file;
110: markedLine = nl.line;
111: printLineMarker(markedFile, markedLine);
112: markedPhysical = line();
113: assert effectiveLine() == nl.line;
114: }
115: assert markedFile.equals(nl.file) && effectiveLine() <= nl.line;
116: while (effectiveLine() < nl.line)
117: super .pln();
118: if (blankLine) {
119: if (Constants.FIRST_COLUMN == column)
120: indent = nlColumn;
121: blankLine = false;
122: }
123: assert column <= nlColumn;
124: while (column < nlColumn)
125: super .p(' ');
126: assert effectiveLine() == nl.line && column == nlColumn;
127: }
128:
129: public Printer indent() {
130: if (indent < 0)
131: indent = 0;
132: return this ;
133: }
134:
135: public Printer indentLess() {
136: if (indent < Constants.INDENTATION)
137: indent = Constants.INDENTATION;
138: return this ;
139: }
140:
141: public Printer indentMore() {
142: if (indent < 0)
143: indent = 0;
144: return this ;
145: }
146:
147: public Printer p(final Attribute a) {
148: return printNode(a);
149: }
150:
151: public Printer p(final char c) {
152: if (blankLine && ' ' == c) {
153: indent++;
154: return this ;
155: }
156: endBlank();
157: return super .p(c);
158: }
159:
160: public Printer p(final Comment c) {
161: return printNode(c);
162: }
163:
164: public Printer p(final double d) {
165: endBlank();
166: return super .p(d);
167: }
168:
169: public Printer p(final int i) {
170: endBlank();
171: return super .p(i);
172: }
173:
174: public Printer p(final long l) {
175: endBlank();
176: return super .p(l);
177: }
178:
179: public Printer p(final Node n) {
180: return printNode(n);
181: }
182:
183: public Printer p(final String s) {
184: endBlank();
185: return super .p(s);
186: }
187:
188: public Printer pln() {
189: blankLine = true;
190: return this ;
191: }
192:
193: public Printer pln(final char c) {
194: return p(c).pln();
195: }
196:
197: public Printer pln(final double d) {
198: return p(d).pln();
199: }
200:
201: public Printer pln(final int i) {
202: return p(i).pln();
203: }
204:
205: public Printer pln(final long l) {
206: return p(l).pln();
207: }
208:
209: public Printer pln(final String s) {
210: return p(s).pln();
211: }
212:
213: public void printLineMarker(final String file, final long line) {
214: if (Constants.FIRST_COLUMN < column)
215: super .pln();
216: final boolean isJava = visitor instanceof JavaPrinter;
217: super .p(isJava ? "//#line " : "# ");
218: final int sepIndex = file.lastIndexOf('/');
219: final boolean keepFile = !isJava || showFilePaths
220: || -1 == sepIndex;
221: final String fileName = keepFile ? file : file
222: .substring(sepIndex + 1);
223: super .p(line + " \"" + fileName + "\"");
224: super .pln();
225: blankLine = true;
226: }
227:
228: protected Printer printNode(final Node n) {
229: final boolean outerNodeHasLocation = currentNodeHasLocation;
230: currentNodeHasLocation = null != n && n.hasLocation();
231: if (currentNodeHasLocation) {
232: lastFile = n.getLocation().file;
233: lastLine = n.getLocation().line;
234: }
235: if (n instanceof LineMarker) {
236: final LineMarker m = (LineMarker) n;
237: assert n.getLocation().file.equals(m.file)
238: && n.getLocation().line == m.line - 1;
239: indent = 0;
240: markedFile = m.file;
241: markedLine = m.line;
242: markedPhysical = line + 2;
243: } else {
244: ensureLocation(n);
245: }
246: super .p(n);
247: currentNodeHasLocation = outerNodeHasLocation;
248: return this ;
249: }
250:
251: public Printer reset() {
252: blankLine = true;
253: return super .reset();
254: }
255:
256: public Printer sep() {
257: blankLine = true;
258: return super.sep();
259: }
260: }
|