/****************************************************************************** * Copyright (C) 2013 Fabio Zadrozny and others * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Fabio Zadrozny <fabiofz@gmail.com> - initial API and implementation * Alexander Kurtakov <akurtako@redhat.com> - ongoing maintenance ******************************************************************************/ package org.python.pydev.shared_core.parsing; import java.util.ArrayList; import java.util.List; import org.eclipse.core.resources.IMarker; import org.eclipse.core.resources.IResource; import org.eclipse.core.runtime.Assert; import org.eclipse.core.runtime.CoreException; import org.eclipse.jface.text.DocumentEvent; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.IDocumentListener; import org.python.pydev.shared_core.log.Log; import org.python.pydev.shared_core.model.ISimpleNode; import org.python.pydev.shared_core.structure.Tuple; /** * 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. */ public abstract class BaseParser implements IParser { /** * just for tests, when we don't have any editor */ public static boolean ACCEPT_NULL_INPUT_EDITOR = false; /** * this is the document we should parse */ protected volatile IDocument document; /** * ast for the last successful parsing */ protected ISimpleNode root = null; /** * listens to changes in the document */ protected IDocumentListener documentListener; /** * listeners that get notified of successful or unsuccessful parser achievements */ protected ArrayList<IParserObserver> parserListeners; /** * this is the object that will keep parser schedules for us (and will call us for doing parsing when requested) */ protected ParserScheduler scheduler; /** * indicates we should do analysis only on doc save */ protected boolean useAnalysisOnlyOnDocSave; /** * Identifies whether this parser is disposed. */ protected volatile boolean disposed = false; /** * Should only be called for testing. Does not register as a thread. */ protected BaseParser(BaseParserManager parseManager) { parserListeners = new ArrayList<IParserObserver>(); scheduler = new ParserScheduler(this, parseManager); documentListener = new IDocumentListener() { @Override public void documentChanged(DocumentEvent event) { if (useAnalysisOnlyOnDocSave) { //if we're doing analysis only on doc change, the parser will not give any changes //to the scheduler, so, we won't have any parse events to respond to return; } String text = event.getText(); boolean parseNow = true; if (event == null || text == null) { parseNow = false; } if (parseNow) { if (text.indexOf("\n") == -1 && text.indexOf("\r") == -1) { parseNow = false; } } if (!parseNow) { // carriage return in changed text means parse now, anything // else means parse later scheduler.parseLater(); } else { scheduler.parseNow(); } } @Override public void documentAboutToBeChanged(DocumentEvent event) { } }; } /** * should be called when the editor is disposed */ @Override public void dispose() { this.disposed = true; this.scheduler.dispose(); // remove the listeners if (document != null) { document.removeDocumentListener(documentListener); } synchronized (parserListeners) { parserListeners.clear(); } } public ISimpleNode getRoot() { return root; } @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); } /** * This is the input from the editor that we're using in the parse */ protected/*IEditorInput*/Object input; @Override public void setDocument(IDocument document, Object input) { setDocument(document, true, input); } public synchronized void setDocument(IDocument doc, boolean addToScheduler, Object input) { this.input = input; // Cleans up old listeners if (this.document != null) { this.document.removeDocumentListener(documentListener); } // Set up new listener this.document = doc; if (doc == null) { Log.log("No document in PyParser::setDocument?"); return; } doc.addDocumentListener(documentListener); if (addToScheduler) { // Reparse document on the initial set (force it) scheduler.parseNow(true); } } // ---------------------------------------------------------------------------- listeners /** stock listener implementation */ @Override public void addParseListener(IParserObserver listener) { Assert.isNotNull(listener); synchronized (parserListeners) { if (!parserListeners.contains(listener)) { parserListeners.add(listener); } } } /** stock listener implementation */ @Override public void removeParseListener(IParserObserver listener) { Assert.isNotNull(listener); synchronized (parserListeners) { parserListeners.remove(listener); } } // ---------------------------------------------------------------------------- notifications /** * stock listener implementation event is fired whenever we get a new root * @param original */ protected void fireParserChanged(ChangedParserInfoForObservers info) { this.root = info.root; List<IParserObserver> temp; synchronized (parserListeners) { temp = new ArrayList<IParserObserver>(parserListeners); } for (IParserObserver l : temp) { try { //work on a copy (because listeners may want to remove themselves and we cannot afford concurrent modifications here) if (l instanceof IParserObserver3) { ((IParserObserver3) l).parserChanged(info); } else if (l instanceof IParserObserver2) { ((IParserObserver2) l).parserChanged(info.root, info.file, info.doc, info.argsToReparse); } else { l.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 */ protected void fireParserError(ErrorParserInfoForObservers info) { List<IParserObserver> temp; synchronized (parserListeners) { temp = new ArrayList<IParserObserver>(parserListeners); } for (IParserObserver l : temp) {//work on a copy (because listeners may want to remove themselves and we cannot afford concurrent modifications here) if (l instanceof IParserObserver3) { ((IParserObserver3) l).parserError(info); } else if (l instanceof IParserObserver2) { ((IParserObserver2) l).parserError(info.error, info.file, info.doc, info.argsToReparse); } else { l.parserError(info.error, info.file, info.doc); } } } // ---------------------------------------------------------------------------- parsing public static class ParseOutput { public final long modificationStamp; public final Throwable error; public final ISimpleNode ast; public ParseOutput(Tuple<ISimpleNode, Throwable> astInfo, long modificationStamp) { this.ast = astInfo.o1; this.error = astInfo.o2; this.modificationStamp = modificationStamp; } public ParseOutput(ISimpleNode ast, Throwable error, long modificationStamp) { this.ast = ast; this.error = error; this.modificationStamp = modificationStamp; } public ParseOutput() { this.ast = null; this.error = null; this.modificationStamp = -1; } } /** * 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 abstract ParseOutput reparseDocument(Object... argsToReparse); /** * This function will remove the markers related to errors. * @param resource the file that should have the markers removed * @throws CoreException */ public static void deleteErrorMarkers(IResource resource) throws CoreException { IMarker[] markers = resource.findMarkers(IMarker.PROBLEM, false, IResource.DEPTH_ZERO); if (markers.length > 0) { resource.deleteMarkers(IMarker.PROBLEM, false, IResource.DEPTH_ZERO); } } @Override public void resetTimeoutPreferences(boolean useAnalysisOnlyOnDocSave) { this.useAnalysisOnlyOnDocSave = useAnalysisOnlyOnDocSave; } public List<IParserObserver> getObservers() { synchronized (parserListeners) { return new ArrayList<IParserObserver>(this.parserListeners); } } }