/** * Copyright (c) 2005-2013 by Appcelerator, Inc. All Rights Reserved. * Licensed under the terms of the Eclipse Public License (EPL). * Please see the license.txt included with this distribution for details. * Any modifications to this file must keep this entire header intact. */ package org.python.pydev.parser.prettyprinterv2; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.SortedMap; import java.util.TreeMap; import org.eclipse.core.runtime.Assert; import org.python.pydev.parser.jython.ISpecialStr; import org.python.pydev.parser.jython.SimpleNode; import org.python.pydev.parser.jython.ast.commentType; import org.python.pydev.shared_core.string.FastStringBuffer; import org.python.pydev.shared_core.structure.Tuple; /** * This document is the temporary structure we create to put on the tokens and the comments. * * It's line oriented and we should fill it with all things in the proper place (and properly indented) so * that we can just make a simple print later on. */ public class PrettyPrinterDocV2 { /** * Holds the lines to print. */ public final SortedMap<Integer, PrettyPrinterDocLineEntry> linesToColAndContents = new TreeMap<Integer, PrettyPrinterDocLineEntry>(); private Map<Integer, List<ILinePart>> recordedChanges = new HashMap<Integer, List<ILinePart>>(); private int lastRecordedChangesId = 0; public void addBefore(int beginLine, int beginCol, String string, Object token) { PrettyPrinterDocLineEntry lineContents = getLine(beginLine); ILinePart linePart = lineContents.addBefore(beginCol, string, token); addToCurrentRecordedChanges(linePart); } private void addToCurrentRecordedChanges(ILinePart linePart) { for (List<ILinePart> lst : recordedChanges.values()) { lst.add(linePart); } } public void add(int beginLine, int beginCol, String string, Object token) { PrettyPrinterDocLineEntry lineContents = getLine(beginLine); ILinePart linePart = lineContents.add(beginCol, string, token); addToCurrentRecordedChanges(linePart); } public LinePartRequireMark addRequireOneOf(SimpleNode node, String... requireOneOf) { PrettyPrinterDocLineEntry line = getLine(node.beginLine); LinePartRequireMark linePart = line.addRequireMark(node.beginColumn, requireOneOf); addToCurrentRecordedChanges(linePart); return linePart; } public LinePartRequireMark addRequire(String string, SimpleNode node) { checkLine(node); PrettyPrinterDocLineEntry line = getLine(node.beginLine); LinePartRequireMark linePart = line.addRequireMark(node.beginColumn, string); addToCurrentRecordedChanges(linePart); return linePart; } private void checkLine(SimpleNode node) { if (node.beginLine < 0 || node.beginColumn < 0) { throw new RuntimeException("Node: " + node + " has invalid line " + node.beginLine + " or col " + node.beginColumn); } } public LinePartRequireMark addRequireBefore(String string, ILinePart o1) { PrettyPrinterDocLineEntry line = getLine(o1.getLine()); LinePartRequireMark linePart = line.addRequireMarkBefore(o1, string); addToCurrentRecordedChanges(linePart); return linePart; } public LinePartRequireMark addRequireAfter(String string, ILinePart o1) { PrettyPrinterDocLineEntry line = getLine(o1.getLine()); LinePartRequireMark linePart = line.addRequireMarkAfterBefore(o1, string); addToCurrentRecordedChanges(linePart); return linePart; } public LinePartRequireIndentMark addRequireIndent(String string, SimpleNode node) { PrettyPrinterDocLineEntry line = getLine(node.beginLine); LinePartRequireIndentMark linePart = line.addRequireIndentMark(node.beginColumn, string); addToCurrentRecordedChanges(linePart); return linePart; } //---------------- Mark that a statement has started (new lines need a '\') public void addStartStatementMark(ILinePart foundWithLowerLocation, SimpleNode node) { getLine(foundWithLowerLocation.getLine()).addStartStatementMark(foundWithLowerLocation, node); } public void addEndStatementMark(ILinePart foundWithHigherLocation, SimpleNode node) { getLine(foundWithHigherLocation.getLine()).addEndStatementMark(foundWithHigherLocation, node); } //------------ Get information PrettyPrinterDocLineEntry getLine(int beginLine) { if (beginLine < 0) { throw new RuntimeException("Cannot get negative line."); } PrettyPrinterDocLineEntry lineContents = linesToColAndContents.get(beginLine); if (lineContents == null) { lineContents = new PrettyPrinterDocLineEntry(beginLine); linesToColAndContents.put(beginLine, lineContents); } return lineContents; } int getLastLineKey() { return linesToColAndContents.lastKey(); } PrettyPrinterDocLineEntry getLastLine(boolean considerOnlyCommentOrEmptyLines) { Integer line = linesToColAndContents.lastKey(); PrettyPrinterDocLineEntry last = null; if (line != null) { for (; line >= linesToColAndContents.firstKey(); line--) { PrettyPrinterDocLineEntry found = linesToColAndContents.get(line); if (found != null) { last = found; List<ILinePart> sortedParts = last.getSortedParts(); if (sortedParts.size() == 0) { continue; } for (ILinePart iLinePart : sortedParts) { if (!(iLinePart.getToken() instanceof commentType)) { return last; } } } } } return last; } public ILinePart getLastPart() { PrettyPrinterDocLineEntry lastLine = getLastLine(true); java.util.List<ILinePart> sortedParts = lastLine.getSortedParts(); return sortedParts.get(sortedParts.size() - 1); } //------------ Indentation public void addIndent(SimpleNode node) { PrettyPrinterDocLineEntry line = getLine(node.beginLine); line.indent(node); } public LinePartIndentMark addIndent(SimpleNode node, boolean requireNewLine) { PrettyPrinterDocLineEntry line = getLine(node.beginLine); return line.indent(node, requireNewLine); } public LinePartIndentMark addDedent() { return addDedent(0); } public LinePartIndentMark addDedent(int emptyLinesRequiredAfterDedent) { PrettyPrinterDocLineEntry lastLine = getLastLine(false); return lastLine.dedent(emptyLinesRequiredAfterDedent); } //------------ toString @Override public String toString() { FastStringBuffer buf = new FastStringBuffer(); buf.append("PrettyPrinterDocV2[\n"); Set<Entry<Integer, PrettyPrinterDocLineEntry>> entrySet = linesToColAndContents.entrySet(); for (Entry<Integer, PrettyPrinterDocLineEntry> entry : entrySet) { buf.append(entry.getKey() + ": " + entry.getValue() + "\n"); } return "PrettyPrinterDocV2[" + buf + "]"; } //------------ Changes Recording public int pushRecordChanges() { lastRecordedChangesId++; recordedChanges.put(lastRecordedChangesId, new ArrayList<ILinePart>()); return lastRecordedChangesId; } /** * @return The line parts recorded. Guaranteed to be sorted by line/col. */ public List<ILinePart> popRecordChanges(int id) { List<ILinePart> ret = recordedChanges.remove(id); Collections.sort(ret, new Comparator<ILinePart>() { @Override public int compare(ILinePart o1, ILinePart o2) { if (o1.getLine() < o2.getLine()) { return -1; } if (o2.getLine() < o1.getLine()) { return 1; } //same line if (o1.getBeginCol() < o2.getBeginCol()) { return -1; } if (o2.getBeginCol() < o1.getBeginCol()) { return 1; } return 0; } }); return ret; } public int replaceRecorded(List<ILinePart> recordChanges, String... replacements) { int replaced = 0; Assert.isTrue(replacements.length % 2 == 0); for (ILinePart linePart : recordChanges) { if (linePart instanceof ILinePart2) { ILinePart2 iLinePart2 = (ILinePart2) linePart; for (int i = 0; i < replacements.length; i += 2) { String toReplace = replacements[i]; String newToken = replacements[i + 1]; if (iLinePart2.getString().equals(toReplace)) { iLinePart2.setString(newToken); replaced += 1; } } } } return replaced; } public Tuple<ILinePart, ILinePart> getLowerAndHigerFound(List<ILinePart> recordChanges) { return getLowerAndHigerFound(recordChanges, true); } public Tuple<ILinePart, ILinePart> getLowerAndHigerFound(List<ILinePart> recordChanges, boolean acceptToken) { Tuple<ILinePart, ILinePart> lowerAndHigher = null; ILinePart foundWithLowerLocation = null; ILinePart foundWithHigherLocation = null; for (ILinePart p : recordChanges) { if (p.getToken() instanceof commentType) { continue; } if (!acceptToken) { if (p.getToken() instanceof ISpecialStr) { continue; } } if (foundWithHigherLocation == null) { foundWithHigherLocation = p; } else if (p.getLine() > foundWithHigherLocation.getLine()) { foundWithHigherLocation = p; } else if (p.getLine() == foundWithHigherLocation.getLine() && p.getBeginCol() > foundWithHigherLocation.getBeginCol()) { foundWithHigherLocation = p; } if (foundWithLowerLocation == null) { foundWithLowerLocation = p; } else if (p.getLine() < foundWithLowerLocation.getLine()) { foundWithLowerLocation = p; } else if (p.getLine() == foundWithLowerLocation.getLine() && p.getBeginCol() < foundWithLowerLocation.getBeginCol()) { foundWithLowerLocation = p; } } if (foundWithLowerLocation != null && foundWithHigherLocation != null) { lowerAndHigher = new Tuple<ILinePart, ILinePart>(foundWithLowerLocation, foundWithHigherLocation); } return lowerAndHigher; } /** * In this method, all the require marks have to be either already given in the parsing * (and removed from the line) or should be replaced by actual nodes. */ public void validateRequireMarks() { if (linesToColAndContents.size() == 0) { return;//nothing to validate (no entries there) } Tuple<ILinePart, Boolean> search = null; for (int line = linesToColAndContents.firstKey(); line <= linesToColAndContents.lastKey(); line++) { PrettyPrinterDocLineEntry prettyPrinterDocLineEntry = linesToColAndContents.get(line); if (prettyPrinterDocLineEntry == null) { continue; } List<ILinePart> parts = prettyPrinterDocLineEntry.getSortedParts(); for (int position = 0; position < parts.size(); position++) { ILinePart iLinePart = parts.get(position); if (iLinePart instanceof LinePartRequireMark) { LinePartRequireMark linePartRequireMark = (LinePartRequireMark) iLinePart; Tuple<ILinePart, Boolean> lastSearch = search; //Ok, go forwards and see if we have a match somewhere search = search(line, position, linePartRequireMark, false, lastSearch); boolean found = search.o2; if (!found) { search = search(line, position, linePartRequireMark, true, lastSearch); found = search.o2; } ILinePart next = search.o1; if (!found) { if (iLinePart instanceof LinePartRequireIndentMark) { throw new RuntimeException("Unable to find place to add indent"); } ILinePart removed = parts.remove(position); LinePartRequireAdded linePartRequireAdded = new LinePartRequireAdded(removed.getBeginCol(), linePartRequireMark.getToken(), linePartRequireMark.getToken(), prettyPrinterDocLineEntry); int addAt = position; if (lastSearch != null) { int i = parts.indexOf(lastSearch.o1); if (i > -1 && position == i - 1) { addAt = i + 1; } } parts.add(addAt, linePartRequireAdded); //Generate the search so that the last search is correct. search = new Tuple<ILinePart, Boolean>(linePartRequireAdded, true); } else { if (iLinePart instanceof LinePartRequireIndentMark) { //add the indent on the last position returned PrettyPrinterDocLineEntry l = this.getLine(next.getLine()); l.indentAfter(next, true); } else { search.o1.setMarkAsFound(); } int i = parts.indexOf(iLinePart); parts.remove(i); position--; //make up for the removed part } } } } } private Tuple<ILinePart, Boolean> search(int line, int position, LinePartRequireMark linePartRequireMark, boolean forward, Tuple<ILinePart, Boolean> lastSearch) { boolean found = false; ILinePart next = null; LinePartsIterator it = getLinePartsIterator(line, position, forward, lastSearch); boolean searchForIndentMark = linePartRequireMark instanceof LinePartRequireIndentMark; OUTER: while (it.hasNext() && !found) { next = it.next(); if (next instanceof ILinePart2) { if (!searchForIndentMark && next instanceof LinePartRequireAdded) { break; //As the require parts are in order, finding a previously added require, we just mark it as not found. } if (!searchForIndentMark) { if (next.isMarkedAsFound()) { continue; } } ILinePart2 part2 = (ILinePart2) next; if (linePartRequireMark.requireOneOf != null) { for (String s : linePartRequireMark.requireOneOf) { if (part2.getString().equals(s)) { found = true; break OUTER; } } } else { if (part2.getString().equals(linePartRequireMark.getToken())) { found = true; break; } } Object token = next.getToken(); if (token instanceof SimpleNode && !(token instanceof commentType)) { break; //didn't find it. } } } return new Tuple<ILinePart, Boolean>(next, found); } public LinePartsIterator getLinePartsIterator(int initialLine, int initialPos, boolean forward, Tuple<ILinePart, Boolean> lastSearch) { return new LinePartsIterator(this, initialLine, initialPos, forward, lastSearch); } public LinePartIndentMark getLastDedent() { for (int line = this.getLastLineKey(); line >= linesToColAndContents.firstKey(); line--) { PrettyPrinterDocLineEntry prettyPrinterDocLineEntry = linesToColAndContents.get(line); if (prettyPrinterDocLineEntry != null) { List<ILinePart> sortedParts = prettyPrinterDocLineEntry.getSortedParts(); for (int i = sortedParts.size() - 1; i >= 0; i--) { ILinePart iLinePart = sortedParts.get(i); if (iLinePart instanceof LinePartIndentMark) { LinePartIndentMark linePartIndentMark = (LinePartIndentMark) iLinePart; if (!linePartIndentMark.isIndent()) { return linePartIndentMark; } } } } } return null; } } /** * Helper class to iterate over the line parts in sequence (forward or backward) while traversing the lines. */ class LinePartsIterator implements Iterator<ILinePart> { private int line; private int position; List<ILinePart> currentPart; private PrettyPrinterDocV2 doc; private ILinePart next; private boolean forward; private Tuple<ILinePart, Boolean> lastSearch; public LinePartsIterator(PrettyPrinterDocV2 prettyPrinterDocV2, int initialLine, int initialPos, boolean forward, Tuple<ILinePart, Boolean> lastSearch) { this.doc = prettyPrinterDocV2; this.line = initialLine; this.position = initialPos; this.forward = forward; this.lastSearch = lastSearch; calcNext(); } private void calcNext() { next = null; if (forward) { for (; line <= doc.linesToColAndContents.lastKey(); line++) { PrettyPrinterDocLineEntry prettyPrinterDocLineEntry = doc.linesToColAndContents.get(line); if (prettyPrinterDocLineEntry == null) { continue; } List<ILinePart> parts = prettyPrinterDocLineEntry.getSortedParts(); int onlyAfter = lastSearch != null ? parts.indexOf(lastSearch.o1) : -1; while (next == null) { if (position < parts.size()) { if (position < onlyAfter) { position++; continue; } next = parts.get(position); position++; return; } if (position >= parts.size()) { position = 0; break; } } } } else {//backward for (; line >= doc.linesToColAndContents.firstKey(); line--) { PrettyPrinterDocLineEntry prettyPrinterDocLineEntry = doc.linesToColAndContents.get(line); if (prettyPrinterDocLineEntry == null) { continue; } List<ILinePart> parts = prettyPrinterDocLineEntry.getSortedParts(); int onlyAfter = lastSearch != null ? parts.indexOf(lastSearch.o1) : -1; while (next == null) { if (position >= parts.size()) { position = parts.size() - 1; } if (position < onlyAfter) { return; //going backwards, when we reach the onlyAfter position, there's nowhere to go. } if (position >= 0) { next = parts.get(position); position--; return; } if (position < 0) { position = Integer.MAX_VALUE; break; } } } } } @Override public void remove() { throw new RuntimeException("Not impl"); } @Override public ILinePart next() { ILinePart ret = next; calcNext(); return ret; } @Override public boolean hasNext() { return next != null; } }