package xtc.tree;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.io.Writer;
import xtc.Constants;
import xtc.lang.JavaPrinter;
/**
* A printer that guarantees that each Node is printed at the <em>exact</em>
* file, line, and column specified by its location. Where necessary, this
* printer prints line markers (similar to those used by the C preprocessor:<br>
*
* <tt>#</tt> <i>line</i> <tt>"</tt><i>file</i><tt>"</tt><br>
*
* Reproducing exact source locations helps downstream tools track symbolic
* information for error messages, debugging, and exception backtraces. Even
* though this printer makes a best effort to print nodes without locations
* nicely, experience shows that the generated code is usually harder to read
* for humans (less "pretty") than with the superclass xtc.tree.Printer.
*/
public class LineupPrinter extends Printer {
/**
* Is the current line blank? If yes, the printer delays any indentation until
* it encounters the first non-blank character on the line.
*/
protected boolean blankLine = true;
/** Does the node currently being visited have a location? */
protected boolean currentNodeHasLocation = true;
/** File name specified by the last node that had a location. */
protected String lastFile = null;
/** Line number specified by the last node that had a location. */
protected long lastLine = 0;
/** File name specified by the last emitted line marker. */
protected String markedFile = null;
/** Line number specified by the last emitted line marker. */
protected long markedLine = 0;
/** Line number in the actual output after the last emitted line marker. */
protected long markedPhysical = 0;
protected boolean showFilePaths = true;
public LineupPrinter(final OutputStream out, final boolean showFilePaths) {
this(new PrintWriter(out, false), showFilePaths);
}
public LineupPrinter(final PrintWriter out, final boolean showFilePaths) {
super(out);
this.showFilePaths = showFilePaths;
}
public LineupPrinter(final Writer out, final boolean showFilePaths) {
this(new PrintWriter(out, false), showFilePaths);
}
public Printer align(final int desiredColumn) {
if (blankLine) {
indent = desiredColumn;
return this;
}
return super.align(desiredColumn);
}
/** Current line in the output adjusted for line markers. */
protected long effectiveLine() {
return markedLine + line - markedPhysical;
}
/** If blankLine is true, print any deferred line breaks and blank characters. */
protected void endBlank() {
if (blankLine) {
if (Constants.FIRST_COLUMN < column) {
if (!currentNodeHasLocation && null != lastFile)
/* make it appear to debugger as if line+file stand still */
printLineMarker(lastFile, lastLine);
else
super.pln();
}
blankLine = false;
super.indent();
}
}
/**
* If the given node has a location, ensure that the output file, line, and
* column is at exactly that location, by emitting some combination of line
* markers, line breaks, and blanks.
*/
protected void ensureLocation(final Node n) {
if (null == n || !n.hasLocation())
return;
final Location nl = n.getLocation();
/* Note: columns in locations are 1-based, whereas printer columns are based
* on Constants.FIRST_COLUMN. Therefore, this code adjusts nl.column. */
final int nlColumn = nl.column + Constants.FIRST_COLUMN - 1;
if (null == markedFile || !markedFile.equals(nl.file)
|| nl.line < effectiveLine() || effectiveLine() + 2 < nl.line
|| nl.line == effectiveLine() && nlColumn < column) {
markedFile = nl.file;
markedLine = nl.line;
printLineMarker(markedFile, markedLine);
markedPhysical = line();
assert effectiveLine() == nl.line;
}
assert markedFile.equals(nl.file) && effectiveLine() <= nl.line;
while (effectiveLine() < nl.line)
super.pln();
if (blankLine) {
if (Constants.FIRST_COLUMN == column)
indent = nlColumn;
blankLine = false;
}
assert column <= nlColumn;
while (column < nlColumn)
super.p(' ');
assert effectiveLine() == nl.line && column == nlColumn;
}
public Printer indent() {
if (indent < 0)
indent = 0;
return this;
}
public Printer indentLess() {
if (indent < Constants.INDENTATION)
indent = Constants.INDENTATION;
return this;
}
public Printer indentMore() {
if (indent < 0)
indent = 0;
return this;
}
public Printer p(final Attribute a) {
return printNode(a);
}
public Printer p(final char c) {
if (blankLine && ' ' == c) {
indent++;
return this;
}
endBlank();
return super.p(c);
}
public Printer p(final Comment c) {
return printNode(c);
}
public Printer p(final double d) {
endBlank();
return super.p(d);
}
public Printer p(final int i) {
endBlank();
return super.p(i);
}
public Printer p(final long l) {
endBlank();
return super.p(l);
}
public Printer p(final Node n) {
return printNode(n);
}
public Printer p(final String s) {
endBlank();
return super.p(s);
}
public Printer pln() {
blankLine = true;
return this;
}
public Printer pln(final char c) {
return p(c).pln();
}
public Printer pln(final double d) {
return p(d).pln();
}
public Printer pln(final int i) {
return p(i).pln();
}
public Printer pln(final long l) {
return p(l).pln();
}
public Printer pln(final String s) {
return p(s).pln();
}
public void printLineMarker(final String file, final long line) {
if (Constants.FIRST_COLUMN < column)
super.pln();
final boolean isJava = visitor instanceof JavaPrinter;
super.p(isJava ? "//#line " : "# ");
final int sepIndex = file.lastIndexOf('/');
final boolean keepFile = !isJava || showFilePaths || -1 == sepIndex;
final String fileName = keepFile ? file : file.substring(sepIndex + 1);
super.p(line + " \"" + fileName + "\"");
super.pln();
blankLine = true;
}
protected Printer printNode(final Node n) {
final boolean outerNodeHasLocation = currentNodeHasLocation;
currentNodeHasLocation = null != n && n.hasLocation();
if (currentNodeHasLocation) {
lastFile = n.getLocation().file;
lastLine = n.getLocation().line;
}
if (n instanceof LineMarker) {
final LineMarker m = (LineMarker) n;
assert n.getLocation().file.equals(m.file) && n.getLocation().line == m.line - 1;
if (Constants.FIRST_COLUMN < column)
super.pln();
indent = 0;
markedFile = m.file;
markedLine = m.line;
markedPhysical = line + 2;
} else {
ensureLocation(n);
}
super.p(n);
currentNodeHasLocation = outerNodeHasLocation;
return this;
}
public Printer reset() {
blankLine = true;
return super.reset();
}
public Printer sep() {
blankLine = true;
return super.sep();
}
}