/**
* 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.
*/
/*
* Author: atotic
* Created: Jul 25, 2003
*/
package org.python.pydev.parser;
import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.eclipse.core.internal.resources.ResourceException;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.jface.preference.PreferenceStore;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IDocumentExtension4;
import org.eclipse.jface.text.IRegion;
import org.eclipse.ui.IFileEditorInput;
import org.eclipse.ui.texteditor.MarkerUtilities;
import org.python.pydev.core.ExtensionHelper;
import org.python.pydev.core.IGrammarVersionProvider;
import org.python.pydev.core.IGrammarVersionProvider.AdditionalGrammarVersionsToCheck;
import org.python.pydev.core.IPyEdit;
import org.python.pydev.core.IPythonNature;
import org.python.pydev.core.MisconfigurationException;
import org.python.pydev.core.log.Log;
import org.python.pydev.core.parser.IPyParser;
import org.python.pydev.parser.fastparser.FastParser;
import org.python.pydev.parser.grammar24.PythonGrammar24;
import org.python.pydev.parser.grammar25.PythonGrammar25;
import org.python.pydev.parser.grammar26.PythonGrammar26;
import org.python.pydev.parser.grammar27.PythonGrammar27;
import org.python.pydev.parser.grammar30.PythonGrammar30;
import org.python.pydev.parser.grammar36.PythonGrammar36;
import org.python.pydev.parser.jython.FastCharStream;
import org.python.pydev.parser.jython.ParseException;
import org.python.pydev.parser.jython.SimpleNode;
import org.python.pydev.parser.jython.Token;
import org.python.pydev.parser.jython.TokenMgrError;
import org.python.pydev.parser.jython.ast.Module;
import org.python.pydev.parser.jython.ast.stmtType;
import org.python.pydev.shared_core.callbacks.ICallback;
import org.python.pydev.shared_core.io.FileUtils;
import org.python.pydev.shared_core.model.ErrorDescription;
import org.python.pydev.shared_core.model.ISimpleNode;
import org.python.pydev.shared_core.out_of_memory.OnExpectedOutOfMemory;
import org.python.pydev.shared_core.parsing.BaseParser;
import org.python.pydev.shared_core.parsing.ChangedParserInfoForObservers;
import org.python.pydev.shared_core.parsing.ErrorParserInfoForObservers;
import org.python.pydev.shared_core.parsing.IParserObserver;
import org.python.pydev.shared_core.parsing.IParserObserver2;
import org.python.pydev.shared_core.parsing.IParserObserver3;
import org.python.pydev.shared_core.string.StringUtils;
import org.python.pydev.shared_core.structure.LowMemoryArrayList;
import org.python.pydev.shared_core.structure.Tuple;
import org.python.pydev.shared_core.structure.Tuple3;
/**
* PyParser uses org.python.parser to parse the document (lexical analysis) It
* is attached to PyEdit (a view), and it listens to document changes On every
* document change, the syntax tree is regenerated The reparsing of the document
* is done on a ParsingThread
*
* Clients that need to know when new parse tree has been generated should
* register as parseListeners.
*/
@SuppressWarnings("restriction")
public class PyParser extends BaseParser implements IPyParser {
/**
* Just for tests: show whenever we're not able to parse some file.
*/
public static boolean DEBUG_SHOW_PARSE_ERRORS = false;
/**
* Defines whether we should use the fast stream or not
*/
public static boolean USE_FAST_STREAM = true;
/**
* used to enable tracing in the grammar
*/
public static boolean ENABLE_TRACING = false;
/**
* This is the version of the grammar that should be used for this parser
*/
private final IGrammarVersionProvider grammarVersionProvider;
public static String getGrammarVersionStr(int grammarVersion) {
if (grammarVersion == IGrammarVersionProvider.GRAMMAR_PYTHON_VERSION_2_4) {
return "grammar: Python 2.4";
} else if (grammarVersion == IGrammarVersionProvider.GRAMMAR_PYTHON_VERSION_2_5) {
return "grammar: Python 2.5";
} else if (grammarVersion == IGrammarVersionProvider.GRAMMAR_PYTHON_VERSION_2_6) {
return "grammar: Python 2.6";
} else if (grammarVersion == IGrammarVersionProvider.GRAMMAR_PYTHON_VERSION_2_7) {
return "grammar: Python 2.7";
} else if (grammarVersion == IGrammarVersionProvider.GRAMMAR_PYTHON_VERSION_3_0) {
return "grammar: Python 3.0 - 3.5";
} else if (grammarVersion == IGrammarVersionProvider.GRAMMAR_PYTHON_VERSION_3_6) {
return "grammar: Python 3.6";
} else if (grammarVersion == IGrammarVersionProvider.GRAMMAR_PYTHON_VERSION_CYTHON) {
return "grammar: Cython";
} else {
return "grammar: unrecognized: " + grammarVersion;
}
}
public int getGrammarVersion() throws MisconfigurationException {
return grammarVersionProvider.getGrammarVersion();
}
/**
* Should only be called for testing. Does not register as a thread.
*/
public PyParser(IGrammarVersionProvider grammarVersionProvider) {
super(PyParserManager.getPyParserManager(new PreferenceStore()));
if (grammarVersionProvider == null) {
grammarVersionProvider = new IGrammarVersionProvider() {
@Override
public int getGrammarVersion() {
return IPythonNature.LATEST_GRAMMAR_VERSION;
}
@Override
public AdditionalGrammarVersionsToCheck getAdditionalGrammarVersions()
throws MisconfigurationException {
return null;
}
};
}
this.grammarVersionProvider = grammarVersionProvider;
}
/**
* Ok, create the parser for an editor
*
* @param editorView
*/
public PyParser(IPyEdit editorView) {
this(getGrammarProviderFromEdit(editorView));
}
/**
* @param editorView this is the editor that we're getting in the parser
* @return a provider signaling the grammar to be used for the parser.
* @throws MisconfigurationException
*/
private static IGrammarVersionProvider getGrammarProviderFromEdit(IPyEdit editorView) {
return editorView.getGrammarVersionProvider();
}
@Override
public void notifySaved() {
//force parse on save
forceReparse();
}
/**
* @return false if we asked a reparse and it will not be scheduled because a reparse is already in action.
*/
@Override
public boolean forceReparse(Object... argsToReparse) {
if (disposed) {
return true; //reparse didn't happen, but no matter what happens, it won't happen anyways
}
return scheduler.parseNow(true, argsToReparse);
}
public static interface IPostParserListener {
public void participantsNotified(Object... argsToReparse);
}
private final List<IPostParserListener> postParserListeners = new LowMemoryArrayList<>();
private final Object lockPostParserListeners = new Object();
public void addPostParseListener(IPostParserListener iParserObserver) {
synchronized (lockPostParserListeners) {
postParserListeners.add(iParserObserver);
}
}
public void removePostParseListener(IPostParserListener iPostParserListener) {
synchronized (lockPostParserListeners) {
postParserListeners.remove(iPostParserListener);
}
}
/**
* stock listener implementation event is fired whenever we get a new root
* @param original
*/
@Override
@SuppressWarnings("unchecked")
protected void fireParserChanged(ChangedParserInfoForObservers info) {
super.fireParserChanged(info);
List<IParserObserver> participants = ExtensionHelper.getParticipants(ExtensionHelper.PYDEV_PARSER_OBSERVER);
for (IParserObserver observer : participants) {
try {
if (observer instanceof IParserObserver3) {
((IParserObserver3) observer).parserChanged(info);
} else if (observer instanceof IParserObserver2) {
((IParserObserver2) observer).parserChanged(info.root, info.file, info.doc, info.argsToReparse);
} else {
observer.parserChanged(info.root, info.file, info.doc, info.docModificationStamp);
}
} catch (Exception e) {
Log.log(e);
}
}
}
/**
* stock listener implementation event is fired when parse fails
* @param original
*/
@Override
@SuppressWarnings("unchecked")
protected void fireParserError(ErrorParserInfoForObservers info) {
super.fireParserError(info);
List<IParserObserver> participants = ExtensionHelper.getParticipants(ExtensionHelper.PYDEV_PARSER_OBSERVER);
for (IParserObserver observer : participants) {
if (observer instanceof IParserObserver3) {
((IParserObserver3) observer).parserError(info);
} else if (observer instanceof IParserObserver2) {
((IParserObserver2) observer).parserError(info.error, info.file, info.doc, info.argsToReparse);
} else {
observer.parserError(info.error, info.file, info.doc);
}
}
}
/**
* Parses the document, generates error annotations
*
* @param argsToReparse: will be passed to fireParserError / fireParserChanged so that the IParserObserver2
* can check it. This is useful when the reparse was done with some specific thing in mind, so that its requestor
* can pass some specific thing to the parser observers
*
* @return a tuple with the SimpleNode root(if parsed) and the error (if any).
* if we are able to recover from a reparse, we have both, the root and the error.
*/
@Override
public ParseOutput reparseDocument(Object... argsToReparse) {
//get the document ast and error in object
int version;
AdditionalGrammarVersionsToCheck additionalGrammarsToCheck = null;
try {
version = grammarVersionProvider.getGrammarVersion();
additionalGrammarsToCheck = grammarVersionProvider.getAdditionalGrammarVersions();
} catch (MisconfigurationException e1) {
//Ok, we cannot get it... let's put on the default
version = IGrammarVersionProvider.LATEST_GRAMMAR_VERSION;
}
long documentTime = System.currentTimeMillis();
ParseOutput obj = reparseDocument(new ParserInfo(document, version, true, additionalGrammarsToCheck));
IFile original = null;
IAdaptable adaptable = null;
if (input == null) {
return obj;
}
original = (input instanceof IFileEditorInput) ? ((IFileEditorInput) input).getFile() : null;
if (original != null) {
adaptable = original;
} else {
//probably an external file, may have some location provider mechanism
//it may be org.eclipse.ui.internal.editors.text.JavaFileEditorInput
adaptable = (IAdaptable) input;
}
//delete the markers
if (original != null) {
try {
deleteErrorMarkers(original);
} catch (ResourceException e) {
//ok, if it is a resource exception, it may have happened because the resource does not exist anymore
//so, there is no need to log this failure
if (original.exists()) {
Log.log(e);
}
} catch (CoreException e) {
Log.log(e);
}
} else if (adaptable == null) {
//ok, we have nothing... maybe we are in tests...
if (!PyParser.ACCEPT_NULL_INPUT_EDITOR) {
throw new RuntimeException("Null input editor received in parser!");
}
}
//end delete the markers
if (disposed) {
//if it was disposed in this time, don't fire any notification nor return anything valid.
return new ParseOutput();
}
ErrorParserInfoForObservers errorInfo = null;
if (obj.error instanceof ParseException || obj.error instanceof TokenMgrError) {
errorInfo = new ErrorParserInfoForObservers(obj.error, adaptable, document, argsToReparse);
}
if (obj.ast != null) {
//Ok, reparse successful, lets erase the markers that are in the editor we just parsed
//Note: we may get the ast even if errors happen (and we'll notify in that case too).
ChangedParserInfoForObservers info = new ChangedParserInfoForObservers(obj.ast, obj.modificationStamp,
adaptable, document, documentTime, errorInfo, argsToReparse);
fireParserChanged(info);
}
if (errorInfo != null) {
fireParserError(errorInfo);
}
if (postParserListeners.size() > 0) {
ArrayList<IPostParserListener> tempList = new ArrayList<>(postParserListeners);
for (IPostParserListener iParserObserver : tempList) {
iParserObserver.participantsNotified(argsToReparse);
}
}
return obj;
}
//static methods that can be used to get the ast (and error if any) --------------------------------------
public final static class ParserInfo {
public IDocument document;
/**
* A set with the lines that were changed when trying to make the document parseable
*/
public final Set<Integer> linesChanged = new HashSet<Integer>();
/**
* This is the version of the grammar to be used
* @see IPythonNature.GRAMMAR_XXX constants
*/
public final int grammarVersion;
/**
* The module name of the contents parsed (may be null)
*/
public final String moduleName;
/**
* The file that's been parsed (may be null)
*/
public final File file;
/**
* Whether we should generate the tree as a parse result or we're just interested in errors.
*/
public final boolean generateTree;
public final AdditionalGrammarVersionsToCheck additionalGrammarVersionsToCheck;
/**
* @param grammarVersion: see IPythonNature.GRAMMAR_XXX constants
*/
public ParserInfo(IDocument document, int grammarVersion,
AdditionalGrammarVersionsToCheck additionalGrammarVersionsToCheck) {
this(document, grammarVersion, null, null, true, additionalGrammarVersionsToCheck);
}
public ParserInfo(IDocument document, IGrammarVersionProvider nature) throws MisconfigurationException {
this(document, nature.getGrammarVersion(), nature.getAdditionalGrammarVersions());
}
public ParserInfo(IDocument document, IGrammarVersionProvider nature, String moduleName, File file)
throws MisconfigurationException {
this(document, nature.getGrammarVersion(), moduleName, file, true, nature.getAdditionalGrammarVersions());
}
public ParserInfo(IDocument document, int grammarVersion, String name, File f, boolean generateTree,
AdditionalGrammarVersionsToCheck additionalGrammarVersionsToCheck) {
this.document = document;
this.grammarVersion = grammarVersion;
this.moduleName = name;
this.file = f;
this.generateTree = generateTree;
this.additionalGrammarVersionsToCheck = additionalGrammarVersionsToCheck;
}
public ParserInfo(IDocument document, IGrammarVersionProvider grammarProvider, boolean generateTree)
throws MisconfigurationException {
this(document, grammarProvider.getGrammarVersion(), null, null, generateTree,
grammarProvider.getAdditionalGrammarVersions());
}
public ParserInfo(IDocument document, int grammarVersion, boolean generateTree,
AdditionalGrammarVersionsToCheck additionalGrammarVersionsToCheck) {
this(document, grammarVersion, null, null, generateTree, additionalGrammarVersionsToCheck);
}
@Override
public String toString() {
StringBuffer buf = new StringBuffer();
buf.append("ParserInfo [");
buf.append("file:");
buf.append(file);
buf.append("\nmoduleName:");
buf.append(moduleName);
if (!generateTree) {
buf.append(" NOT GENERATING TREE");
}
buf.append("]");
return buf.toString();
}
}
/**
* This list of callbacks is mostly used for testing, so that we can check what's been parsed.
*/
public final static List<ICallback<Object, Tuple3<ISimpleNode, Throwable, ParserInfo>>> successfulParseListeners = new ArrayList<ICallback<Object, Tuple3<ISimpleNode, Throwable, ParserInfo>>>();
/**
* Create the char array to parse based on the initial document and our parser limitations.
*/
private static char[] createCharArrayToParse(String startDoc) {
int length = startDoc.length();
int skipAtStart = 0;
if (startDoc.startsWith(FileUtils.BOM_UTF8)) {
skipAtStart = FileUtils.BOM_UTF8.length();
} else if (startDoc.startsWith(FileUtils.BOM_UNICODE)) {
skipAtStart = FileUtils.BOM_UNICODE.length();
}
int addAtEnd = 0;
if (!startDoc.endsWith("\n") && !startDoc.endsWith("\r")) {
addAtEnd = 1;
}
char[] charArray = new char[length - skipAtStart + addAtEnd];
startDoc.getChars(skipAtStart, length, charArray, 0);
if (addAtEnd > 0) {
charArray[charArray.length - 1] = '\n';
}
return charArray;
}
/**
* Actually creates the grammar.
* @param generateTree whether we should generate the AST or not.
*/
public static IGrammar createGrammar(boolean generateTree, int grammarVersion, char[] charArray) {
IGrammar grammar;
FastCharStream in = new FastCharStream(charArray);
switch (grammarVersion) {
case IPythonNature.GRAMMAR_PYTHON_VERSION_2_4:
grammar = new PythonGrammar24(generateTree, in);
break;
case IPythonNature.GRAMMAR_PYTHON_VERSION_2_5:
grammar = new PythonGrammar25(generateTree, in);
break;
case IPythonNature.GRAMMAR_PYTHON_VERSION_2_6:
grammar = new PythonGrammar26(generateTree, in);
break;
case IPythonNature.GRAMMAR_PYTHON_VERSION_2_7:
grammar = new PythonGrammar27(generateTree, in);
break;
case IPythonNature.GRAMMAR_PYTHON_VERSION_3_0:
grammar = new PythonGrammar30(generateTree, in);
break;
case IPythonNature.GRAMMAR_PYTHON_VERSION_3_6:
grammar = new PythonGrammar36(generateTree, in);
break;
//case CYTHON: not treated here (only in reparseDocument).
default:
throw new RuntimeException("The grammar specified for parsing is not valid: " + grammarVersion);
}
if (ENABLE_TRACING) {
//grammar has to be generated with debugging info for this to make a difference
grammar.enable_tracing();
}
return grammar;
}
/**
* Note: this method should generally not be needed. Use reparseDocument on most situation (this
* is mostly for tests or profilings).
*/
public static Tuple<SimpleNode, IGrammar> reparseDocumentInternal(IDocument doc, boolean generateTree,
int grammarVersion)
throws ParseException {
char[] charArray = createCharArrayToParse(doc.get());
IGrammar grammar = createGrammar(generateTree, grammarVersion, charArray);
return new Tuple<SimpleNode, IGrammar>(grammar.file_input(), grammar); // parses the file
}
/**
* @return a tuple with the SimpleNode root(if parsed) and the error (if any).
* if we are able to recover from a reparse, we have both, the root and the error.
*/
public static ParseOutput reparseDocument(ParserInfo info) {
if (info.grammarVersion == IPythonNature.GRAMMAR_PYTHON_VERSION_CYTHON) {
IDocument doc = info.document;
return new ParseOutput(createCythonAst(doc), ((IDocumentExtension4) info.document).getModificationStamp());
}
// create a stream with document's data
//Note: safer could be locking, but if for some reason we get the modification stamp and the document changes
//right after that, at least any cache will check against the old stamp to be reconstructed (which is the main
//reason for this stamp).
long modifiedTime = ((IDocumentExtension4) info.document).getModificationStamp();
String startDoc = info.document.get();
if (startDoc.trim().length() == 0) {
//If empty, don't bother to parse!
return new ParseOutput(new Module(new stmtType[0]), null, modifiedTime);
}
Set<Integer> parsedVersions = new HashSet<>();
char[] charArray;
try {
charArray = createCharArrayToParse(startDoc);
} catch (OutOfMemoryError e1) {
OnExpectedOutOfMemory.clearCacheOnOutOfMemory.call(null);
charArray = createCharArrayToParse(startDoc); //retry now with caches cleared...
}
startDoc = null; //it can be garbage-collected now.
Tuple<ISimpleNode, Throwable> returnVar = new Tuple<ISimpleNode, Throwable>(null, null);
IGrammar grammar = null;
try {
parsedVersions.add(info.grammarVersion);
grammar = createGrammar(info.generateTree, info.grammarVersion, charArray);
SimpleNode newRoot;
try {
newRoot = grammar.file_input();
} catch (OutOfMemoryError e) {
OnExpectedOutOfMemory.clearCacheOnOutOfMemory.call(null);
newRoot = grammar.file_input(); //retry now with caches cleared...
}
returnVar.o1 = newRoot;
//only notify successful parses
if (successfulParseListeners.size() > 0) {
Tuple3<ISimpleNode, Throwable, ParserInfo> param = new Tuple3<ISimpleNode, Throwable, ParserInfo>(
returnVar.o1, returnVar.o2, info);
for (ICallback<Object, Tuple3<ISimpleNode, Throwable, ParserInfo>> callback : successfulParseListeners) {
callback.call(param);
}
}
returnVar.o2 = grammar.getErrorOnParsing();
if (returnVar.o2 == null) {
AdditionalGrammarVersionsToCheck additionalGrammarVersionsToCheck = info.additionalGrammarVersionsToCheck;
if (additionalGrammarVersionsToCheck != null) {
for (int grammarVersion : additionalGrammarVersionsToCheck.getGrammarVersions()) {
if (parsedVersions.contains(grammarVersion)) {
continue;
}
parsedVersions.add(grammarVersion);
grammar = createGrammar(false, grammarVersion, charArray);
try {
grammar.file_input();
} catch (OutOfMemoryError e) {
OnExpectedOutOfMemory.clearCacheOnOutOfMemory.call(null);
grammar.file_input(); //retry now with caches cleared...
}
returnVar.o2 = grammar.getErrorOnParsing();
if (returnVar.o2 != null) {
break;
}
}
}
}
} catch (Throwable e) {
//ok, some error happened when trying the parse... let's go and clear the local info before doing
//another parse.
if (DEBUG_SHOW_PARSE_ERRORS) {
e.printStackTrace();
}
//If the grammar was not created, the problem wasn't in the parsing... so, let's just rethrow the error
if (grammar == null) {
throw new RuntimeException(e);
}
//We have to change it for the 1st error we got (the one in catch is the last one).
Throwable errorOnParsing = grammar.getErrorOnParsing();
if (errorOnParsing != null) {
e = errorOnParsing;
} else if (DEBUG_SHOW_PARSE_ERRORS) {
System.out.println("Unhandled error");
e.printStackTrace();
}
grammar = null;
if (e instanceof ParseException || e instanceof TokenMgrError) {
returnVar = new Tuple<ISimpleNode, Throwable>(null, e);
} else if (e.getClass().getName().indexOf("LookaheadSuccess") != -1) {
//don't log this kind of error...
} else {
Log.log(e);
}
}
if (DEBUG_SHOW_PARSE_ERRORS) {
if (returnVar.o1 == null) {
System.out.println("Unable to parse " + info);
}
}
// System.out.println("Output grammar: "+returnVar);
return new ParseOutput(returnVar, modifiedTime);
}
public static Tuple<ISimpleNode, Throwable> createCythonAst(IDocument doc) {
List<stmtType> classesAndFunctions = FastParser.parseCython(doc);
return new Tuple<ISimpleNode, Throwable>(new Module(
classesAndFunctions.toArray(new stmtType[classesAndFunctions
.size()])),
null);
}
/**
* Adds the error markers for some error that was found in the parsing process.
*
* @param error the error find while parsing the document
* @param resource the resource that should have the error added
* @param doc the document with the resource contents
* @return the error description (or null)
*
* @throws BadLocationException
* @throws CoreException
*/
public static ErrorDescription createParserErrorMarkers(Throwable error, IAdaptable resource, IDocument doc) {
ErrorDescription errDesc;
errDesc = createErrorDesc(error, doc);
//Create marker only if possible...
if (resource != null) {
IResource fileAdapter = resource.getAdapter(IResource.class);
if (fileAdapter != null) {
try {
Map<String, Object> map = new HashMap<String, Object>();
map.put(IMarker.MESSAGE, errDesc.message);
map.put(IMarker.SEVERITY, IMarker.SEVERITY_ERROR);
map.put(IMarker.LINE_NUMBER, errDesc.errorLine);
map.put(IMarker.CHAR_START, errDesc.errorStart);
map.put(IMarker.CHAR_END, errDesc.errorEnd);
map.put(IMarker.TRANSIENT, true);
MarkerUtilities.createMarker(fileAdapter, map, IMarker.PROBLEM);
} catch (Exception e) {
Log.log(e);
}
}
}
return errDesc;
}
/**
* Creates the error description for a given error in the parse.
*
* Must return an error!
*/
public static ErrorDescription createErrorDesc(Throwable error, IDocument doc) {
try {
int errorStart = -1;
int errorEnd = -1;
int errorLine = -1;
String message = null;
int tokenBeginLine = -1;
if (error instanceof ParseException) {
ParseException parseErr = (ParseException) error;
message = parseErr.getMessage();
// Figure out where the error is in the document, and create a
// marker for it
if (parseErr.currentToken == null) {
try {
IRegion endLine = doc.getLineInformationOfOffset(doc.getLength());
errorStart = endLine.getOffset();
errorEnd = endLine.getOffset() + endLine.getLength();
} catch (BadLocationException e) {
//ignore (can have changed in the meanwhile)
}
} else {
Token errorToken = parseErr.currentToken.next != null ? parseErr.currentToken.next
: parseErr.currentToken;
if (errorToken != null) {
tokenBeginLine = errorToken.beginLine - 1;
try {
IRegion startLine = doc.getLineInformation(getDocPosFromAstPos(errorToken.beginLine));
IRegion endLine;
if (errorToken.endLine == 0) {
endLine = startLine;
} else {
endLine = doc.getLineInformation(getDocPosFromAstPos(errorToken.endLine));
}
errorStart = startLine.getOffset() + getDocPosFromAstPos(errorToken.beginColumn);
errorEnd = endLine.getOffset() + errorToken.endColumn;
} catch (BadLocationException e) {
//ignore (can have changed in the meanwhile)
}
}
}
} else if (error instanceof TokenMgrError) {
TokenMgrError tokenErr = (TokenMgrError) error;
message = tokenErr.getMessage();
tokenBeginLine = tokenErr.errorLine - 1;
try {
IRegion startLine = doc.getLineInformation(tokenErr.errorLine - 1);
errorStart = startLine.getOffset();
errorEnd = startLine.getOffset() + tokenErr.errorColumn;
} catch (BadLocationException e) {
//ignore (can have changed in the meanwhile)
}
} else {
Log.log("Error, expecting ParseException or TokenMgrError. Received: " + error);
return new ErrorDescription("Internal PyDev Error", 0, 0, 0);
}
try {
errorLine = doc.getLineOfOffset(errorStart) + 1;
} catch (BadLocationException e) {
errorLine = tokenBeginLine;
}
// map.put(IMarker.LOCATION, "Whassup?"); this is the location field
// in task manager
if (message != null) { // prettyprint
message = StringUtils.replaceNewLines(message, " ");
}
return new ErrorDescription(message, errorLine, errorStart, errorEnd);
} catch (Exception e) {
Log.log(e);
return new ErrorDescription("Internal PyDev Error", 0, 0, 0);
}
}
/**
* The ast position starts at 1 and the document starts at 0 (but it could be that we had nothing valid
* and received an invalid position, so, we must treat that).
*/
private static int getDocPosFromAstPos(int astPos) {
if (astPos > 0) {
astPos--;
}
return astPos;
}
}