/** * 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. */ /* * Created on Jan 19, 2005 * * @author Fabio Zadrozny */ package org.python.pydev.editor.codecompletion.revisited.visitors; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Set; import java.util.Stack; import org.python.pydev.core.ILocalScope; import org.python.pydev.core.IModule; import org.python.pydev.core.docutils.PySelection; import org.python.pydev.core.structure.FastStack; import org.python.pydev.parser.jython.SimpleNode; import org.python.pydev.parser.jython.ast.Assign; import org.python.pydev.parser.jython.ast.Call; import org.python.pydev.parser.jython.ast.ClassDef; import org.python.pydev.parser.jython.ast.FunctionDef; import org.python.pydev.parser.jython.ast.Global; import org.python.pydev.parser.jython.ast.ImportFrom; import org.python.pydev.parser.jython.ast.Module; import org.python.pydev.parser.jython.ast.Name; import org.python.pydev.parser.jython.ast.NameTok; import org.python.pydev.parser.jython.ast.NameTokType; import org.python.pydev.parser.jython.ast.Subscript; import org.python.pydev.parser.jython.ast.Tuple; import org.python.pydev.parser.jython.ast.aliasType; import org.python.pydev.parser.jython.ast.exprType; import org.python.pydev.parser.visitors.NodeUtils; /** * @author Fabio Zadrozny */ public class FindDefinitionModelVisitor extends AbstractVisitor { /** * This is the token to find. */ private String tokenToFind; /** * List of definitions. */ public List<Definition> definitions = new ArrayList<Definition>(); /** * Stack of classes / methods to get to a definition. */ private FastStack<SimpleNode> defsStack = new FastStack<SimpleNode>(20); /** * This is a stack that will keep the globals for each stack */ private FastStack<Set<String>> globalDeclarationsStack = new FastStack<Set<String>>(20); /** * This is the module we are visiting: just a weak reference so that we don't create a cycle (let's * leave things easy for the garbage collector). */ private WeakReference<IModule> module; /** * It is only available if the cursor position is upon a NameTok in an import (it represents the complete * path for finding the module from the current module -- it can be a regular or relative import). */ public String moduleImported; private int line; private int col; private boolean foundAsDefinition = false; private Definition definitionFound; /** * Call is stored for the context for a keyword parameter */ private Stack<Call> call = new Stack<Call>(); /** * Constructor * @param line: starts at 1 * @param col: starts at 1 */ public FindDefinitionModelVisitor(String token, int line, int col, IModule module) { this.tokenToFind = token; this.module = new WeakReference<IModule>(module); this.line = line; this.col = col; this.moduleName = module.getName(); //we may have a global declared in the global scope globalDeclarationsStack.push(new HashSet<String>()); } @Override public Object visitImportFrom(ImportFrom node) throws Exception { String modRep = NodeUtils.getRepresentationString(node.module); if (NodeUtils.isWithin(line, col, node.module)) { //it is a token in the definition of a module int startingCol = node.module.beginColumn; int endingCol = startingCol; while (endingCol < this.col) { endingCol++; } int lastChar = endingCol - startingCol; moduleImported = modRep.substring(0, lastChar); int i = lastChar; while (i < modRep.length()) { if (Character.isJavaIdentifierPart(modRep.charAt(i))) { i++; } else { break; } } moduleImported += modRep.substring(lastChar, i); } else { //it was not the module, so, we have to check for each name alias imported for (aliasType alias : node.names) { //we do not check the 'as' because if it is some 'as', it will be gotten as a global in the module if (NodeUtils.isWithin(line, col, alias.name)) { moduleImported = modRep + "." + NodeUtils.getRepresentationString(alias.name); } } } return super.visitImportFrom(node); } /** * @see org.python.pydev.parser.jython.ast.VisitorBase#unhandled_node(org.python.pydev.parser.jython.SimpleNode) */ protected Object unhandled_node(SimpleNode node) throws Exception { return null; } /** * @see org.python.pydev.parser.jython.ast.VisitorBase#traverse(org.python.pydev.parser.jython.SimpleNode) */ public void traverse(SimpleNode node) throws Exception { node.traverse(this); } /** * @see org.python.pydev.parser.jython.ast.VisitorBase#visitClassDef(org.python.pydev.parser.jython.ast.ClassDef) */ public Object visitClassDef(ClassDef node) throws Exception { globalDeclarationsStack.push(new HashSet<String>()); defsStack.push(node); node.traverse(this); defsStack.pop(); globalDeclarationsStack.pop(); checkDeclaration(node, (NameTok) node.name); return null; } /** * @see org.python.pydev.parser.jython.ast.VisitorBase#visitFunctionDef(org.python.pydev.parser.jython.ast.FunctionDef) */ public Object visitFunctionDef(FunctionDef node) throws Exception { globalDeclarationsStack.push(new HashSet<String>()); defsStack.push(node); if (node.args != null) { if (node.args.args != null) { for (exprType arg : node.args.args) { if (arg instanceof Name) { checkParam((Name) arg); } } } if (node.args.kwonlyargs != null) { for (exprType arg : node.args.kwonlyargs) { if (arg instanceof Name) { checkParam((Name) arg); } } } } node.traverse(this); defsStack.pop(); globalDeclarationsStack.pop(); checkDeclaration(node, (NameTok) node.name); return null; } /** * @param node the declaration node we're interested in (class or function) * @param name the token that represents the name of that declaration */ private void checkParam(Name name) { String rep = NodeUtils.getRepresentationString(name); if (rep.equals(tokenToFind) && line == name.beginLine && col >= name.beginColumn && col <= name.beginColumn + rep.length()) { foundAsDefinition = true; // if it is found as a definition it is an 'exact' match, so, erase all the others. ILocalScope scope = new LocalScope(this.defsStack); for (Iterator<Definition> it = definitions.iterator(); it.hasNext();) { Definition d = it.next(); if (!d.scope.equals(scope)) { it.remove(); } } definitionFound = new Definition(line, name.beginColumn, rep, name, scope, module.get()); definitions.add(definitionFound); } } @Override public Object visitCall(Call node) throws Exception { this.call.push(node); Object r = super.visitCall(node); this.call.pop(); return r; } @Override public Object visitNameTok(NameTok node) throws Exception { if (node.ctx == NameTok.KeywordName) { if (this.line == node.beginLine && this.call.size() > 0) { String rep = NodeUtils.getRepresentationString(node); if (PySelection.isInside(col, node.beginColumn, rep.length())) { foundAsDefinition = true; // if it is found as a definition it is an 'exact' match, so, erase all the others. ILocalScope scope = new LocalScope(this.defsStack); for (Iterator<Definition> it = definitions.iterator(); it.hasNext();) { Definition d = it.next(); if (!d.scope.equals(scope)) { it.remove(); } } definitions.clear(); definitionFound = new KeywordParameterDefinition(line, node.beginColumn, rep, node, scope, module.get(), this.call.peek()); definitions.add(definitionFound); throw new StopVisitingException(); } } } return null; } /** * @param node the declaration node we're interested in (class or function) * @param name the token that represents the name of that declaration */ private void checkDeclaration(SimpleNode node, NameTok name) { String rep = NodeUtils.getRepresentationString(node); if (rep.equals(tokenToFind) && ((line == -1 && col == -1) || (line == name.beginLine && col >= name.beginColumn && col <= name.beginColumn + rep.length()))) { foundAsDefinition = true; // if it is found as a definition it is an 'exact' match, so, erase all the others. ILocalScope scope = new LocalScope(this.defsStack); for (Iterator<Definition> it = definitions.iterator(); it.hasNext();) { Definition d = it.next(); if (!d.scope.equals(scope)) { it.remove(); } } definitionFound = new Definition(name.beginLine, name.beginColumn, rep, node, scope, module.get()); definitions.add(definitionFound); } } @Override public Object visitGlobal(Global node) throws Exception { for (NameTokType n : node.names) { globalDeclarationsStack.peek().add(NodeUtils.getFullRepresentationString(n)); } return null; } @Override public Object visitModule(Module node) throws Exception { this.defsStack.push(node); return super.visitModule(node); } /** * @see org.python.pydev.parser.jython.ast.VisitorBase#visitAssign(org.python.pydev.parser.jython.ast.Assign) */ public Object visitAssign(Assign node) throws Exception { ILocalScope scope = new LocalScope(this.defsStack); if (foundAsDefinition && !scope.equals(definitionFound.scope)) { //if it is found as a definition it is an 'exact' match, so, we do not keep checking it return null; } for (int i = 0; i < node.targets.length; i++) { exprType target = node.targets[i]; if (target instanceof Subscript) { continue; //assigning to an element and not the variable itself. E.g.: mydict[1] = 10 (instead of mydict = 10) } if (target instanceof Tuple) { //if assign is xxx, yyy = 1, 2 //let's separate those as different assigns and analyze one by one Tuple targetTuple = (Tuple) target; if (node.value instanceof Tuple) { Tuple valueTuple = (Tuple) node.value; checkTupleAssignTarget(targetTuple, valueTuple.elts); } else if (node.value instanceof org.python.pydev.parser.jython.ast.List) { org.python.pydev.parser.jython.ast.List valueList = (org.python.pydev.parser.jython.ast.List) node.value; checkTupleAssignTarget(targetTuple, valueList.elts); } else { checkTupleAssignTarget(targetTuple, new exprType[] { node.value }); } } else { String rep = NodeUtils.getFullRepresentationString(target); if (tokenToFind.equals(rep)) { //note, order of equals is important (because one side may be null). exprType nodeValue = node.value; String value = NodeUtils.getFullRepresentationString(nodeValue); if (value == null) { value = ""; } //get the line and column correspondent to the target int line = NodeUtils.getLineDefinition(target); int col = NodeUtils.getColDefinition(target); AssignDefinition definition = new AssignDefinition(value, rep, i, node, line, col, scope, module.get(), nodeValue); //mark it as global (if it was found as global in some of the previous contexts). for (Set<String> globals : globalDeclarationsStack) { if (globals.contains(rep)) { definition.foundAsGlobal = true; } } definitions.add(definition); } } } return null; } /** * Analyze an assign that has the target as a tuple and the multiple elements in the other side. * * E.g.: www, yyy = 1, 2 * * @param targetTuple the target in the assign * @param valueElts the values that are being assigned */ private void checkTupleAssignTarget(Tuple targetTuple, exprType[] valueElts) throws Exception { if (valueElts == null || valueElts.length == 0) { return; //nothing to do if we don't have any values } for (int i = 0; i < targetTuple.elts.length; i++) { int j = i; //that's if the number of values is less than the number of assigns (actually, that'd //probably be an error, but let's go on gracefully, as the user can be in an invalid moment //in his code) if (j >= valueElts.length) { j = valueElts.length - 1; } Assign assign = new Assign(new exprType[] { targetTuple.elts[i] }, valueElts[j]); assign.beginLine = targetTuple.beginLine; assign.beginColumn = targetTuple.beginColumn; visitAssign(assign); } } }