001package daikon.tools.jtb;
002
003import java.util.List;
004import jtb.syntaxtree.*;
005import jtb.visitor.*;
006import org.plumelib.util.StringsPlume;
007
008/**
009 * InsertCommentFormatter is a visitor that does not actually insert comments, but instead corrects
010 * positioning fields of all the tokens in the tree to accomodate already-inserted comments, while
011 * modifying the formatting as little as possible. (It edits the {begin,end}{Line,Column} fields.)
012 *
013 * <p>Each inserted comment either affects only the rest of its line -- by shifting all subsequent
014 * characters rightward -- or only subsequent lines -- by shifting lines downward.
015 *
016 * <p>The caller must supply the collection of inserted comments for recognition by this visitor.
017 */
018public class InsertCommentFormatter extends DepthFirstVisitor {
019  private boolean debugInsert = false;
020
021  private List<NodeToken> comments;
022  private int columnshift = 0;
023  private int lineshift = 0;
024  private int columnshiftline = -1; // the line currently being column-shifted.
025
026  // Column shifting only applies to a single line, then is turned off again.
027  // States for the variables:
028  // columnshift == 0, columnshiftline == -1:
029  //    no column shifting being done
030  // columnshift != 0, columnshiftline == -1:
031  //    column shifting being done, but first real token not yet found
032  // columnshift != 0, columnshiftline != -1:
033  //    column shifting being done, applies only to specified line
034
035  private static final String lineSep = System.lineSeparator();
036
037  public InsertCommentFormatter(List<NodeToken> comments) {
038    this.comments = comments;
039  }
040
041  /**
042   * Returns the number of lines that this token spans in the source code.
043   *
044   * @param n a NodeToken
045   * @return the number of lines that this token spans in the source code
046   */
047  private static int numLines(NodeToken n) {
048    String image = n.tokenImage;
049    return StringsPlume.count(image, lineSep);
050  }
051
052  private static int numColumns(NodeToken n) {
053    if (numLines(n) > 0) {
054      return 0;
055    } else {
056      return n.tokenImage.length();
057    }
058  }
059
060  @Override
061  public void visit(NodeToken n) {
062    if (debugInsert) {
063      System.out.println(
064          "Visit (at "
065              + n.beginLine
066              + ","
067              + n.beginColumn
068              + ") (in comments = "
069              + comments.contains(n)
070              + ") "
071              + n.tokenImage);
072    }
073
074    // See comment at use of this variable below
075    boolean prev_is_double_slash_comment = false;
076
077    // Handle special tokens first
078    if (n.numSpecials() > 0) { // handles case when n.specialTokens is null
079      for (NodeToken s : n.specialTokens) {
080        visit(s);
081        prev_is_double_slash_comment = s.tokenImage.startsWith("//");
082      }
083    }
084
085    if ((columnshift == 0) && (lineshift == 0)) {
086      // nothing to do
087    } else {
088      if (columnshift != 0) {
089        if (columnshiftline == -1) {
090          columnshiftline = n.beginLine;
091        }
092        if (columnshiftline != n.beginLine) {
093          columnshift = 0;
094          columnshiftline = -1;
095        }
096      }
097      n.beginLine += lineshift;
098      n.endLine += lineshift;
099      n.beginColumn += columnshift;
100      n.endColumn += columnshift;
101      if (debugInsert) {
102        System.out.println(
103            "Shifted by " + lineshift + "," + columnshift + ": <<<" + n.tokenImage.trim() + ">>>");
104      }
105    }
106    // Special-case the situation of ending a file with a "//"-style
107    // comment that does not start at the beginning of its line; in that
108    // case, we need to increment the "" token for EOF to start at the next
109    // line.  Otherwise the "" EOF token is marked as starting at the end
110    // of the previous line, though the "//"-style comment doesn't end
111    // until the start of the next line.  Without this code,
112    // jtb.visitor.TreeDumper.visit throws an error.
113    if (n.tokenImage.equals("") && prev_is_double_slash_comment) {
114      assert n.beginLine == n.endLine && n.beginColumn == n.endColumn;
115      n.beginLine += 1;
116      n.beginColumn = 1;
117      n.endLine += 1;
118      n.endColumn = 1;
119    }
120    if (comments.contains(n)) {
121      columnshift += numColumns(n);
122      lineshift += numLines(n);
123    }
124    if (debugInsert) {
125      System.out.println(
126          "End visit (at " + n.beginLine + "," + n.beginColumn + ") " + n.tokenImage);
127    }
128  }
129}