/** * Copyright (c) 2005-2011 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.visitors.scope; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import org.python.pydev.core.structure.FastStack; import org.python.pydev.parser.jython.ISpecialStr; import org.python.pydev.parser.jython.SimpleNode; import org.python.pydev.parser.jython.ast.ClassDef; import org.python.pydev.parser.jython.ast.FunctionDef; import org.python.pydev.parser.jython.ast.Module; import org.python.pydev.parser.jython.ast.VisitorBase; import org.python.pydev.parser.jython.ast.commentType; import org.python.pydev.parser.visitors.NodeUtils; /** * Iterator base. Keeps track of the current scope (method or class). * * This object (and subclasses) are 'disposable', meaning that they should only do 1 visit and after that * only the methods to get the classes should be called. * * @author fabioz */ public abstract class EasyAstIteratorBase extends VisitorBase { protected List<ASTEntry> nodes = new ArrayList<ASTEntry>(); protected final FastStack<SimpleNode> stack = new FastStack<SimpleNode>(20); protected final FastStack<ASTEntry> parents = new FastStack<ASTEntry>(10); protected SimpleNode lastVisited; protected ASTEntry lastDefVisited; //ClassDef or FunctionDef private int higherLine = -1; /** * @see org.python.pydev.parser.jython.ast.VisitorBase#unhandled_node(org.python.pydev.parser.jython.SimpleNode) */ protected Object unhandled_node(SimpleNode node) throws Exception { this.lastVisited = node; int l = NodeUtils.getLineEnd(this.lastVisited); if (l > higherLine) { higherLine = l; } if (node.specialsAfter != null) { for (Object o : node.specialsAfter) { if (o instanceof ISpecialStr) { ISpecialStr str = (ISpecialStr) o; if (str.getBeginLine() > higherLine) { higherLine = str.getBeginLine(); } } } } //the lastDefVisited is only kept if it's really the last definition visited (if there's a node //visited after the last def, it's not kept). if (this.lastDefVisited != null) { this.lastDefVisited = null; } return null; } /** * @see org.python.pydev.parser.jython.ast.VisitorBase#traverse(org.python.pydev.parser.jython.SimpleNode) */ public void traverse(SimpleNode node) throws Exception { if (node instanceof FunctionDef) { traverse((FunctionDef) node); //the order we traverse it is different } else { node.traverse(this); } } /** * @param node * @return */ protected ASTEntry before(SimpleNode node) { ASTEntry entry; entry = createEntry(); entry.node = node; doAddNode(entry); stack.push(node); return entry; } /** * @param entry the entry we're adding. The default implementation adds * the node to the returned nodes (flattened list) */ protected void doAddNode(ASTEntry entry) { nodes.add(entry); } /** * @param entry */ protected void after(ASTEntry entry) { stack.pop(); //only set the end line if it was still not set if (entry.endLine == 0) { int lineEnd = NodeUtils.getLineEnd(lastVisited); if (lineEnd > higherLine) { entry.endLine = lineEnd; } else { entry.endLine = higherLine; } //also make comments found after the node a part of its context. List<Object> s = entry.node.specialsAfter; if (s != null) { for (Object o : s) { if (o instanceof commentType) { commentType comment = (commentType) o; if (comment.beginLine > entry.endLine) { entry.endLine = comment.beginLine; } } } } } this.lastDefVisited = entry; } @Override public Object visitModule(Module node) throws Exception { Object ret = super.visitModule(node); //after visiting the module, let's put the comments to the scope of the last definition found //if there were no other statements out of the class scope. int size = this.nodes.size(); if (size > 0) { int i = -1; if (node.specialsAfter != null) { for (Object o : node.specialsAfter) { if (o instanceof commentType) { commentType type = (commentType) o; if (type.beginLine > i) { i = type.beginLine; } } } } if (i != -1 && this.lastDefVisited != null) { if (lastDefVisited.endLine < i) { lastDefVisited.endLine = i; } } } return ret; } /** * @param node the node we're adding in an 'atomic' way * @return the ast entry that was created in this 'atomic' add */ protected ASTEntry atomic(SimpleNode node) { ASTEntry entry; entry = createEntry(); entry.node = node; entry.endLine = NodeUtils.getLineEnd(node); doAddNode(entry); return entry; } /** * @return the created entry (with its parent set) */ protected ASTEntry createEntry() { ASTEntry entry; if (parents.size() > 0) { entry = new ASTEntry(parents.peek()); } else { entry = new ASTEntry(null); } return entry; } /** * @see org.python.pydev.parser.jython.ast.VisitorBase#visitClassDef(org.python.pydev.parser.jython.ast.ClassDef) */ public Object visitClassDef(ClassDef node) throws Exception { ASTEntry entry = before(node); parents.push(entry); traverse(node); after(entry); parents.pop(); return null; } protected boolean isInGlobal() { Iterator<SimpleNode> iterator = stack.iterator(); while (iterator.hasNext()) { SimpleNode node = (SimpleNode) iterator.next(); if (node instanceof ClassDef || node instanceof FunctionDef) { return false; } } return true; } /** * @return whether we are in a class or method definition scope */ protected boolean isInClassMethodDecl() { Iterator<SimpleNode> iterator = stack.iterator(); while (iterator.hasNext()) { SimpleNode node = (SimpleNode) iterator.next(); if (node instanceof ClassDef) { break; } } while (iterator.hasNext()) { SimpleNode node = (SimpleNode) iterator.next(); if (node instanceof FunctionDef) { return true; } } return false; } /** * @return whether we are in a class definition scope */ protected boolean isInClassDecl() { if (stack.size() == 0) { return false; } SimpleNode last = (SimpleNode) stack.peek(); if (last instanceof ClassDef) { return true; } return false; } /** * @see org.python.pydev.parser.jython.ast.VisitorBase#visitFunctionDef(org.python.pydev.parser.jython.ast.FunctionDef) */ public Object visitFunctionDef(FunctionDef node) throws Exception { ASTEntry entry = before(node); parents.push(entry); traverse(node); parents.pop(); after(entry); return null; } public void traverse(FunctionDef node) throws Exception { if (node.decs != null) { for (int i = 0; i < node.decs.length; i++) { if (node.decs[i] != null) node.decs[i].accept(this); } } if (node.name != null) node.name.accept(this); if (node.args != null) node.args.accept(this); if (node.body != null) { for (int i = 0; i < node.body.length; i++) { if (node.body[i] != null) node.body[i].accept(this); } } } /** * @return and iterator that passes through all the nodes */ public Iterator<ASTEntry> getIterator() { return nodes.iterator(); } /** * @return an iterator for all the classes definitions */ public Iterator<ASTEntry> getClassesIterator() { return getIterator(ClassDef.class); } /** * @return a list with all the class and method definitions */ public List<ASTEntry> getClassesAndMethodsList() { return getAsList(new Class[] { ClassDef.class, FunctionDef.class }); } /** * @param iter this is the iterator we want to get as a list * @return a list with the elements of the iterator */ protected List<ASTEntry> getIteratorAsList(Iterator<ASTEntry> iter) { ArrayList<ASTEntry> list = new ArrayList<ASTEntry>(); while (iter.hasNext()) { list.add(iter.next()); } return list; } /** * @return an iterator for class and method definitions */ public Iterator<ASTEntry> getClassesAndMethodsIterator() { return getIterator(new Class[] { ClassDef.class, FunctionDef.class }); } /** * @return an iterator for method definitions */ public Iterator<ASTEntry> getMethodsIterator() { return getIterator(new Class[] { FunctionDef.class }); } /** * @see EasyASTIteratorVisitor#getIterator(Class[]) */ public Iterator<ASTEntry> getIterator(Class class_) { return getIterator(new Class[] { class_ }); } public List<ASTEntry> getAsList(Class... classes) { List<ASTEntry> newList = new ArrayList<ASTEntry>(); for (Iterator<ASTEntry> iter = nodes.iterator(); iter.hasNext();) { ASTEntry entry = (ASTEntry) iter.next(); if (isFromClass(entry.node, classes)) { newList.add(entry); } } return newList; } public List<ASTEntry> getAsList(Class class_) { return getAsList(new Class[] { class_ }); } /** * @param classes the classes we are searching for * @return an iterator with nodes found from the passed classes */ public Iterator<ASTEntry> getIterator(Class... classes) { return getAsList(classes).iterator(); } /** * @return an iterator that will pass through Name and NameTok tokens */ public Iterator<ASTEntry> getNamesIterator() { return new NameIterator(nodes); } public Iterator<ASTEntry> getOutline() { return new OutlineIterator(nodes); } public List<ASTEntry> getAll() { return nodes; } /** * @return an iterator that will pass all the nodes that were added in this visitor */ public Iterator<ASTEntry> getAllIterator() { return nodes.iterator(); } /** * @param node this is the node we are analyzing * @param classes this are the classes we are looking for * @return true if the node is from one of the passed classes (may be some subclass too) */ @SuppressWarnings("unchecked") protected boolean isFromClass(SimpleNode node, Class[] classes) { Class class1 = node.getClass(); for (int i = 0; i < classes.length; i++) { if (class1.isAssignableFrom(classes[i])) { return true; } } return false; } }