/*************************************************************************** * This file is part of KDevelop * * Copyright 2008 Andreas Pakulat * * Copyright 2006 Roberto Raggi * * Copyright 2006-2008 Hamish Rodda * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #ifndef KDEVPLATFORM_ABSTRACTABSTRACTCONTEXTBUILDER_H #define KDEVPLATFORM_ABSTRACTABSTRACTCONTEXTBUILDER_H #include #include #include #include "../topducontext.h" #include "../duchainpointer.h" #include "../duchainlock.h" #include "../duchain.h" #include "../ducontext.h" #include "../identifier.h" #include "../indexedstring.h" #include "../parsingenvironment.h" namespace KDevelop { /** * \short Abstract definition-use chain context builder class * * The AbstractContextBuilder is a convenience class template for creating customized * definition-use chain context builders from an AST. It simplifies: * - use of your editor integrator * - creating or modifying an existing DUContext tree * - following a DUContext tree for second and subsequent passes, if required * - opening and closing DUContext instances * - tracking which DUContext instances are still present when recompiling, and removing DUContexts which no longer exist in the source code. * * \author Hamish Rodda \ */ template class AbstractContextBuilder { public: /// Constructor. AbstractContextBuilder() : m_compilingContexts( false ) , m_recompiling( false ) , m_lastContext( 0 ) { } /// Destructor. Deletes the editor integrator, if one was created specifically for this builder only. virtual ~AbstractContextBuilder() { } /** * Entry point for building a definition-use chain with this builder. * * This function determines whether we are updating a chain, or creating a new one. If we are * creating a new chain, a new TopDUContext is created and registered with DUChain. * * \param url Url of the document being parsed. * \param node AST node to start building from. * \param updateContext TopDUContext to update if a duchain was previously created for this url, otherwise pass a null pointer. * * \returns the newly created or updated TopDUContext pointer. */ virtual ReferencedTopDUContext build( const IndexedString& url, T* node, ReferencedTopDUContext updateContext = ReferencedTopDUContext() ) { m_compilingContexts = true; m_url = url; ReferencedTopDUContext top; { DUChainWriteLocker lock( DUChain::lock() ); top = updateContext.data(); if( top ) { m_recompiling = true; Q_ASSERT(top->type() == DUContext::Global); Q_ASSERT(DUChain::self()->chainForIndex(top->ownIndex()) == top); } else { top = newTopContext( RangeInRevision( CursorInRevision( 0, 0 ), CursorInRevision( INT_MAX, INT_MAX ) ) ); DUChain::self()->addDocumentChain( top ); top->setType( DUContext::Global ); } setEncountered( top ); setContextOnNode( node, top ); } supportBuild( node, top ); m_compilingContexts = false; return top; } protected: /** * Support another builder by tracking the current context. * @param context the context to use. Must be set when the given node has no context. When it has one attached, this parameter is not needed. */ virtual void supportBuild( T* node, DUContext* context = 0 ) { if (!context) context = contextFromNode(node); Q_ASSERT(context); openContext( context ); startVisiting(node); closeContext(); Q_ASSERT(m_contextStack.isEmpty()); } /** * Entry point to your visitor. Reimplement and call the appropriate visit function. * * \param node AST node to visit. */ virtual void startVisiting( T* node ) = 0; /** * Associate a \a context with a given AST \a node. Once called on a \a node, the * contextFromNode() function should return this \a context when called. * * \param node AST node to associate * \param context DUContext to associate */ virtual void setContextOnNode( T* node, DUContext* context ) = 0; /** * Retrieve an associated DUContext from the given \a node. Used on second and * subsequent passes of the context builder (for supporting other builds) * * \param node AST node which was previously associated * \returns the DUContext which was previously associated */ virtual DUContext* contextFromNode( T* node ) = 0; /** * Retrieves a text range from the given nodes. * * As editor integrators have to be extended to determine ranges from AST nodes, * this function must be reimplemented to allow generic retrieving of rangs from nodes. * * \param fromNode the AST node to start from (on the start boundary) * \param toNode the AST node to end at (on the end boundary) * * \returns the text range encompassing the given AST node(s) */ virtual RangeInRevision editorFindRange( T* fromNode, T* toNode ) = 0; /** * Retrieve a text range for the given nodes. This is a special function required * by c++ support as a different range may need to be retrieved depending on * whether macros are involved. It is not usually required to implement this * function separately to editorFindRange() for other languages. * * \param fromNode the AST node to start from (on the start boundary) * \param toNode the AST node to end at (on the end boundary) * * \returns the text range encompassing the given AST node(s) */ virtual RangeInRevision editorFindRangeForContext( T* fromNode, T* toNode ) { return editorFindRange(fromNode, toNode); } /** * Determine the QualifiedIdentifier which corresponds to the given ast \a node. * * \param node ast node which represents an identifier * \return the qualified identifier determined from \a node */ virtual QualifiedIdentifier identifierForNode( NameT* node ) = 0; /** * Create a new DUContext from the given \a range. * * This exists so that you can create custom DUContext subclasses for your * language if you need to. * * \param range range for the new context to encompass * \returns the newly created context */ virtual DUContext* newContext(const RangeInRevision& range) { return new DUContext(range, currentContext()); } /** * Create a new TopDUContext from the given \a range. * * This exists so that you can create custom TopDUContext subclasses for your * language if you need to. * * \returns the newly created context */ virtual TopDUContext* newTopContext(const RangeInRevision& range, ParsingEnvironmentFile* file = 0) { return new TopDUContext(m_url, range, file); } /// Determine the currently open context. \returns the current context. inline DUContext* currentContext() const { return m_contextStack.top(); } /// Determine the last closed context. \returns the last closed context. inline DUContext* lastContext() const { return m_lastContext; } /// Clears the last closed context. inline void clearLastContext() { m_lastContext = 0; } inline void setLastContext(DUContext* context) { m_lastContext = context; } TopDUContext* topContext() const { return currentContext()->topContext(); } /** * Determine if we are recompiling an existing definition-use chain, or if * a new chain is being created from scratch. * * \returns true if an existing duchain is being updated, otherwise false. */ inline bool recompiling() const { return m_recompiling; } /** * Tell the context builder whether we are recompiling an existing definition-use chain, or if * a new chain is being created from scratch. * * \param recomp set to true if an existing duchain is being updated, otherwise false. */ inline void setRecompiling(bool recomp) { m_recompiling = recomp; } /** * Determine whether this pass will create DUContext instances. * * On the first pass of definition-use chain compiling, DUContext instances * are created to represent contexts in the source code. These contexts are * associated with their AST nodes at the time (see setContextOnNode()). * * On second and subsequent passes, the contexts already exist and thus can be * retrieved through contextFromNode(). * * \returns true if compiling contexts (ie. 1st pass), otherwise false. */ inline bool compilingContexts() const { return m_compilingContexts; } /** * Sets whether we need to create ducontexts, ie. if this is the first pass. * * \sa compilingContexts() */ inline void setCompilingContexts(bool compilingContexts) { m_compilingContexts = compilingContexts; } /** * Create child contexts for only a portion of the document. * * \param node The AST node which corresponds to the context to parse * \param parent The DUContext which encompasses the \a node. * \returns The DUContext which was reparsed, ie. \a parent. */ DUContext* buildSubContexts( T *node, DUContext* parent ) { // m_compilingContexts = true; // m_recompiling = false; setContextOnNode( node, parent ); { openContext( contextFromNode( node ) ); startVisiting( node ); closeContext(); } m_compilingContexts = false; if ( contextFromNode( node ) == parent ) { kDebug() << "Error in AbstractContextBuilder::buildSubContexts(...): du-context was not replaced with new one"; DUChainWriteLocker lock( DUChain::lock() ); deleteContextOnNode( node ); } return contextFromNode( node ); } /** * Delete the DUContext which is associated with the given \a node, * and remove the association. * * \param node Node which is associated with the context to delete. */ void deleteContextOnNode( T* node ) { delete contextFromNode( node ); setContextOnNode( node, 0 ); } /** * Open a context, and create / update it if necessary. * * \param rangeNode The range which encompasses the context. * \param type The type of context to open. * \param identifier The range which encompasses the name of this context, if one exists. * \returns the opened context. */ DUContext* openContext( T* rangeNode, DUContext::ContextType type, NameT* identifier = 0) { if ( m_compilingContexts ) { DUContext* ret = openContextInternal( editorFindRangeForContext( rangeNode, rangeNode ), type, identifier ? identifierForNode( identifier ) : QualifiedIdentifier() ); setContextOnNode( rangeNode, ret ); return ret; } else { openContext( contextFromNode(rangeNode) ); return currentContext(); } } /** * Open a context, and create / update it if necessary. * * \param node The range to associate with the context. * \param range A custom range which the context should encompass. * \param type The type of context to open. * \param identifier The range which encompasses the name of this context, if one exists. * \returns the opened context. */ DUContext* openContext(T* node, const RangeInRevision& range, DUContext::ContextType type, NameT* identifier = 0) { if (m_compilingContexts) { DUContext* ret = openContextInternal(range, type, identifier ? identifierForNode(identifier) : QualifiedIdentifier()); setContextOnNode( node, ret ); return ret; } else { openContext( contextFromNode(node) ); return currentContext(); } } /** * Open a context, and create / update it if necessary. * * \param node The range to associate with the context. * \param range A custom range which the context should encompass. * \param type The type of context to open. * \param identifier The identifier for this context * \returns the opened context. */ DUContext* openContext(T* node, const RangeInRevision& range, DUContext::ContextType type, const QualifiedIdentifier& id) { if (m_compilingContexts) { DUContext* ret = openContextInternal(range, type, id); setContextOnNode( node, ret ); return ret; } else { openContext( contextFromNode(node) ); return currentContext(); } } /** * Open a context, and create / update it if necessary. * * \param rangeNode The range which encompasses the context. * \param type The type of context to open. * \param identifier The identifier which corresponds to the context. * \returns the opened context. */ DUContext* openContext( T* rangeNode, DUContext::ContextType type, const QualifiedIdentifier& identifier ) { if ( m_compilingContexts ) { DUContext* ret = openContextInternal( editorFindRangeForContext( rangeNode, rangeNode ), type, identifier ); setContextOnNode( rangeNode, ret ); return ret; } else { openContext( contextFromNode(rangeNode) ); return currentContext(); } } /** * Open a context, and create / update it if necessary. * * \param fromRange The range which starts the context. * \param toRange The range which ends the context. * \param type The type of context to open. * \param identifier The identifier which corresponds to the context. * \returns the opened context. */ DUContext* openContext( T* fromRange, T* toRange, DUContext::ContextType type, const QualifiedIdentifier& identifier = QualifiedIdentifier() ) { if ( m_compilingContexts ) { DUContext* ret = openContextInternal( editorFindRangeForContext( fromRange, toRange ), type, identifier ); setContextOnNode( fromRange, ret ); return ret; } else { openContext( contextFromNode(fromRange) ); return currentContext(); } } /** * Open a newly created or previously existing context. * * The open context is put on the context stack, and becomes the new * currentContext(). * * \warning When you call this, you also have to open a range! If you want to re-use * the range associated to the context, use injectContext * * \param newContext Context to open. */ virtual void openContext( DUContext* newContext ) { m_contextStack.push( newContext ); m_nextContextStack.push( 0 ); } /** * This can be used to temporarily change the current context. * \param range The range that will be used as new current range, or zero(then the range associated to the context is used) * */ void injectContext( DUContext* ctx ) { openContext( ctx ); } /** * Use this to close the context previously injected with injectContext. * */ void closeInjectedContext() { m_contextStack.pop(); m_nextContextStack.pop(); } /** * Close the current DUContext. When recompiling, this function will remove any * contexts that were not encountered in this passing run. * \note The DUChain write lock is already held here. */ virtual void closeContext() { { DUChainWriteLocker lock( DUChain::lock() ); //Remove all slaves that were not encountered while parsing if(m_compilingContexts) currentContext()->cleanIfNotEncountered( m_encountered ); setEncountered( currentContext() ); m_lastContext = currentContext(); } m_contextStack.pop(); m_nextContextStack.pop(); } /** * Remember that a specific item has been encoutered while parsing. * All items that are not encountered will be deleted at some stage. * * \param item duchain item that was encountered. * */ void setEncountered( DUChainBase* item ) { m_encountered.insert( item ); } /** * Determine whether the given \a item is in the set of encountered items. * * @return true if the \a item has been encountered, otherwise false. * */ bool wasEncountered( DUChainBase* item ) { return m_encountered.contains( item ); } /** * Set the current identifier to \a id. * * \param id the new current identifier. */ void setIdentifier( const QString& id ) { m_identifier = Identifier( id ); m_qIdentifier.push( m_identifier ); } /** * Determine the current identifier. * \returns the current identifier. */ QualifiedIdentifier qualifiedIdentifier() const { return m_qIdentifier; } /** * Clears the current identifier. */ void clearQualifiedIdentifier() { m_qIdentifier.clear(); } /** * Retrieve the current context stack. This function is not expected * to be used often and may be phased out. * * \todo Audit whether access to the context stack is still required, and provide * replacement functionality if possible. */ const QStack& contextStack() const { return m_contextStack; } /** * Access the index of the child context which has been encountered. * * \todo further delineate the role of this function and rename / document better. * \todo make private again? */ int& nextContextIndex() { return m_nextContextStack.top(); } /** * Open a context, either creating it if it does not exist, or referencing a previously existing * context if already encountered in a previous duchain parse run (when recompiling()). * * \param range The range of the context. * \param type The type of context to create. * \param identifier The identifier which corresponds to the context. * \returns the opened context. */ virtual DUContext* openContextInternal( const RangeInRevision& range, DUContext::ContextType type, const QualifiedIdentifier& identifier ) { Q_ASSERT( m_compilingContexts ); DUContext* ret = 0L; { if ( recompiling() ) { DUChainReadLocker readLock( DUChain::lock() ); const QVector& childContexts = currentContext()->childContexts(); int currentIndex = nextContextIndex(); for ( ; currentIndex < childContexts.count(); ++currentIndex ) { DUContext* child = childContexts.at( currentIndex ); RangeInRevision childRange = child->range(); if (child->type() != type) { continue; } // We cannot update a contexts local scope identifier, that will break many other parts, like e.g. // the CodeModel of child contexts or declarations. // For unnamed child-ranges, we still do range-comparison, because we cannot distinguish them in other ways if ((!identifier.isEmpty() && child->localScopeIdentifier() == identifier) || (identifier.isEmpty() && child->localScopeIdentifier().isEmpty() && !childRange.isEmpty() && childRange == range)) { // Match ret = child; readLock.unlock(); DUChainWriteLocker writeLock( DUChain::lock() ); ret->clearImportedParentContexts(); ++currentIndex; break; } } if(ret) nextContextIndex() = currentIndex; //If we had a match, jump forward to that position ///@todo We should also somehow make sure we don't get quadratic worst-case effort while updating. } if ( !ret ) { DUChainWriteLocker writeLock( DUChain::lock() ); ret = newContext( range ); ret->setType( type ); if ( !identifier.isEmpty() ) ret->setLocalScopeIdentifier( identifier ); setInSymbolTable( ret ); }else{ DUChainWriteLocker writeLock( DUChain::lock() ); Q_ASSERT(ret->localScopeIdentifier() == identifier); if(ret->parentContext()) ret->setRange( range ); } } m_encountered.insert( ret ); openContext( ret ); return ret; } ///This function should call context->setInSymbolTable(..) with an appropriate decision. The duchain is write-locked when this is called. virtual void setInSymbolTable(DUContext* context) { if(!context->parentContext()->inSymbolTable()) { context->setInSymbolTable(false); return; } DUContext::ContextType type = context->type(); context->setInSymbolTable(type == DUContext::Class || type == DUContext::Namespace || type == DUContext::Global || type == DUContext::Helper || type == DUContext::Enum); } /// @returns the current url/path ot the document we are parsing IndexedString document() const { return m_url; } private: Identifier m_identifier; IndexedString m_url; QualifiedIdentifier m_qIdentifier; bool m_compilingContexts : 1; bool m_recompiling : 1; QStack m_nextContextStack; DUContext* m_lastContext; //Here all valid declarations/uses/... will be collected QSet m_encountered; QStack m_contextStack; }; } #endif