/** * 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 13/07/2005 */ package org.python.pydev.parser.visitors; import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; import java.util.List; import org.eclipse.core.runtime.Assert; import org.eclipse.jface.text.IDocument; import org.python.pydev.core.FullRepIterable; import org.python.pydev.core.IGrammarVersionProvider; import org.python.pydev.core.MisconfigurationException; import org.python.pydev.core.docutils.PySelection; import org.python.pydev.core.docutils.StringUtils; import org.python.pydev.core.log.Log; import org.python.pydev.parser.jython.ISpecialStr; import org.python.pydev.parser.jython.SimpleNode; import org.python.pydev.parser.jython.ast.Attribute; import org.python.pydev.parser.jython.ast.BinOp; import org.python.pydev.parser.jython.ast.Call; import org.python.pydev.parser.jython.ast.ClassDef; import org.python.pydev.parser.jython.ast.Compare; import org.python.pydev.parser.jython.ast.Dict; import org.python.pydev.parser.jython.ast.Expr; import org.python.pydev.parser.jython.ast.For; import org.python.pydev.parser.jython.ast.FunctionDef; import org.python.pydev.parser.jython.ast.If; import org.python.pydev.parser.jython.ast.Import; import org.python.pydev.parser.jython.ast.ImportFrom; import org.python.pydev.parser.jython.ast.ListComp; 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.Num; import org.python.pydev.parser.jython.ast.Str; import org.python.pydev.parser.jython.ast.Subscript; import org.python.pydev.parser.jython.ast.Suite; import org.python.pydev.parser.jython.ast.TryExcept; import org.python.pydev.parser.jython.ast.TryFinally; import org.python.pydev.parser.jython.ast.Tuple; import org.python.pydev.parser.jython.ast.VisitorBase; import org.python.pydev.parser.jython.ast.While; import org.python.pydev.parser.jython.ast.With; import org.python.pydev.parser.jython.ast.aliasType; import org.python.pydev.parser.jython.ast.commentType; import org.python.pydev.parser.jython.ast.excepthandlerType; import org.python.pydev.parser.jython.ast.exprType; import org.python.pydev.parser.jython.ast.keywordType; import org.python.pydev.parser.jython.ast.stmtType; import org.python.pydev.parser.jython.ast.suiteType; import org.python.pydev.parser.prettyprinterv2.PrettyPrinterV2; import org.python.pydev.parser.visitors.scope.ASTEntry; import org.python.pydev.parser.visitors.scope.EasyASTIteratorVisitor; import org.python.pydev.parser.visitors.scope.EasyASTIteratorWithLoop; import com.aptana.shared_core.string.FastStringBuffer; import com.aptana.shared_core.utils.Reflection; public class NodeUtils { /** * @param node a function definition (if other will return an empty string) * @return a string with the representation of the parameters of the function */ public static String getNodeArgs(SimpleNode node) { if (node instanceof ClassDef) { node = getClassDefInit((ClassDef) node); } if (node instanceof FunctionDef) { FunctionDef f = (FunctionDef) node; String startPar = "( "; FastStringBuffer buffer = new FastStringBuffer(startPar, 40); for (int i = 0; i < f.args.args.length; i++) { if (buffer.length() > startPar.length()) { buffer.append(", "); } buffer.append(getRepresentationString(f.args.args[i])); } buffer.append(" )"); return buffer.toString(); } return ""; } public static String getFullArgs(SimpleNode ast) { if (ast != null) { if (ast instanceof ClassDef) { ast = NodeUtils.getClassDefInit((ClassDef) ast); } if (ast instanceof FunctionDef) { FunctionDef functionDef = (FunctionDef) ast; if (functionDef.args != null) { String printed = PrettyPrinterV2.printArguments(new IGrammarVersionProvider() { public int getGrammarVersion() throws MisconfigurationException { return IGrammarVersionProvider.GRAMMAR_PYTHON_VERSION_3_0; } }, functionDef.args); if (printed != null) { if (!printed.startsWith("(") || !printed.endsWith(")")) { printed = "(" + printed + ")"; } return printed; } } } } return ""; } public static SimpleNode getClassDefInit(ClassDef classDef) { for (stmtType t : classDef.body) { if (t instanceof FunctionDef) { FunctionDef def = (FunctionDef) t; if (((NameTok) def.name).id.equals("__init__")) { return def; } } } return null; } /** * Get the representation for the passed parameter (if it is a String, it is itself, if it * is a SimpleNode, get its representation */ private static String discoverRep(Object o) { if (o instanceof String) { return (String) o; } if (o instanceof NameTok) { return ((NameTok) o).id; } if (o instanceof SimpleNode) { return getRepresentationString((SimpleNode) o); } throw new RuntimeException("Expecting a String or a SimpleNode"); } public static String getRepresentationString(SimpleNode node) { return getRepresentationString(node, false); } /** * @param node this is the node from whom we want to get the representation * @return A suitable String representation for some node. */ public static String getRepresentationString(SimpleNode node, boolean useTypeRepr) { if (node instanceof NameTok) { NameTok tok = (NameTok) node; return tok.id; } if (node instanceof Name) { Name name = (Name) node; return name.id; } if (node instanceof aliasType) { aliasType type = (aliasType) node; return ((NameTok) type.name).id; } if (node instanceof Attribute) { Attribute attribute = (Attribute) node; return discoverRep(attribute.attr); } if (node instanceof keywordType) { keywordType type = (keywordType) node; return discoverRep(type.arg); } if (node instanceof ClassDef) { ClassDef def = (ClassDef) node; return ((NameTok) def.name).id; } if (node instanceof FunctionDef) { FunctionDef def = (FunctionDef) node; return ((NameTok) def.name).id; } if (node instanceof Call) { Call call = ((Call) node); return getRepresentationString(call.func, useTypeRepr); } if (node instanceof org.python.pydev.parser.jython.ast.List || node instanceof ListComp) { String val = "[]"; if (useTypeRepr) { val = getBuiltinType(val); } return val; } if (node instanceof org.python.pydev.parser.jython.ast.Dict) { String val = "{}"; if (useTypeRepr) { val = getBuiltinType(val); } return val; } if (node instanceof BinOp) { BinOp binOp = (BinOp) node; if (binOp.left instanceof Str && binOp.op == BinOp.Mod) { node = binOp.left; //Just change the node... the check below will work with the Str already. } } if (node instanceof Str) { String val; if (useTypeRepr) { val = getBuiltinType("''"); } else { val = "'" + ((Str) node).s + "'"; } return val; } if (node instanceof Tuple) { StringBuffer buf = new StringBuffer(); Tuple t = (Tuple) node; for (exprType e : t.elts) { buf.append(getRepresentationString(e, useTypeRepr)); buf.append(", "); } if (t.elts.length > 0) { int l = buf.length(); buf.deleteCharAt(l - 1); buf.deleteCharAt(l - 2); } String val = "(" + buf + ")"; if (useTypeRepr) { val = getBuiltinType(val); } return val; } if (node instanceof Num) { String val = ((Num) node).n.toString(); if (useTypeRepr) { val = getBuiltinType(val); } return val; } if (node instanceof Import) { aliasType[] names = ((Import) node).names; for (aliasType n : names) { if (n.asname != null) { return ((NameTok) n.asname).id; } return ((NameTok) n.name).id; } } if (node instanceof commentType) { commentType type = (commentType) node; return type.id; } if (node instanceof excepthandlerType) { excepthandlerType type = (excepthandlerType) node; return type.name.toString(); } return null; } /** * @param node * @param t */ public static String getNodeDocString(SimpleNode node) { Str s = getNodeDocStringNode(node); if (s != null) { return s.s; } return null; } public static Str getNodeDocStringNode(SimpleNode node) { Str s = null; stmtType body[] = null; if (node instanceof FunctionDef) { FunctionDef def = (FunctionDef) node; body = def.body; } else if (node instanceof ClassDef) { ClassDef def = (ClassDef) node; body = def.body; } if (body != null && body.length > 0) { if (body[0] instanceof Expr) { Expr e = (Expr) body[0]; if (e.value instanceof Str) { s = (Str) e.value; } } } return s; } public static String getFullRepresentationString(SimpleNode node) { return getFullRepresentationString(node, false); } public static String getFullRepresentationString(SimpleNode node, boolean fullOnSubscriptOrCall) { if (node instanceof Dict) { return "dict"; } if (node instanceof Str || node instanceof Num) { return getRepresentationString(node, true); } if (node instanceof Tuple) { return getRepresentationString(node, true); } if (node instanceof Subscript) { return getFullRepresentationString(((Subscript) node).value); } if (node instanceof Call) { Call c = (Call) node; node = c.func; if (Reflection.hasAttr(node, "value") && Reflection.hasAttr(node, "attr")) { return getFullRepresentationString((SimpleNode) Reflection.getAttrObj(node, "value")) + "." + discoverRep(Reflection.getAttrObj(node, "attr")); } } if (node instanceof Attribute) { //attributes are tricky because we only have backwards access initially, so, we have to: //get it forwards List<SimpleNode> attributeParts = getAttributeParts((Attribute) node); StringBuffer buf = new StringBuffer(); for (Object part : attributeParts) { if (part instanceof Call) { //stop on a call (that's what we usually want, since the end will depend on the things that //return from the call). if (!fullOnSubscriptOrCall) { return buf.toString(); } else { buf.append("()");//call } } else if (part instanceof Subscript) { if (!fullOnSubscriptOrCall) { //stop on a subscript : e.g.: in bb.cc[10].d we only want the bb.cc part return getFullRepresentationString(((Subscript) part).value); } else { buf.append(getFullRepresentationString(((Subscript) part).value)); buf.append("[]");//subscript access } } else { //otherwise, just add another dot and keep going. if (buf.length() > 0) { buf.append("."); } buf.append(getRepresentationString((SimpleNode) part, true)); } } return buf.toString(); } if (node instanceof BinOp) { BinOp binOp = (BinOp) node; if (binOp.left instanceof Str && binOp.op == BinOp.Mod) { //It's something as 'aaa' % (1,2), so, we know it's a string. return getRepresentationString(node, true); } } return getRepresentationString(node, true); } /** * line and col start at 1 */ public static boolean isWithin(int line, int col, SimpleNode node) { int colDefinition = NodeUtils.getColDefinition(node); int lineDefinition = NodeUtils.getLineDefinition(node); int[] colLineEnd = NodeUtils.getColLineEnd(node, false); if (lineDefinition <= line && colDefinition <= col && colLineEnd[0] >= line && colLineEnd[1] >= col) { return true; } return false; } public static SimpleNode getNameTokFromNode(SimpleNode ast2) { if (ast2 instanceof ClassDef) { ClassDef c = (ClassDef) ast2; return c.name; } if (ast2 instanceof FunctionDef) { FunctionDef c = (FunctionDef) ast2; return c.name; } return ast2; } public static int getNameLineDefinition(SimpleNode ast2) { return getLineDefinition(getNameTokFromNode(ast2)); } public static int getNameColDefinition(SimpleNode ast2) { return getColDefinition(getNameTokFromNode(ast2)); } /** * @param ast2 the node to work with * @return the line definition of a node */ public static int getLineDefinition(SimpleNode ast2) { while (ast2 instanceof Attribute) { exprType val = ((Attribute) ast2).value; if (!(val instanceof Call)) { ast2 = val; } else { break; } } if (ast2 instanceof FunctionDef) { return ((FunctionDef) ast2).name.beginLine; } if (ast2 instanceof ClassDef) { return ((ClassDef) ast2).name.beginLine; } return ast2.beginLine; } public static int getColDefinition(SimpleNode ast2) { return getColDefinition(ast2, true); } /** * @param ast2 the node to work with * @return the column definition of a node */ public static int getColDefinition(SimpleNode ast2, boolean always1ForImports) { if (ast2 instanceof Attribute) { //if it is an attribute, we always have to move backward to the first defined token (Attribute.value) exprType value = ((Attribute) ast2).value; return getColDefinition(value); } //call and subscript are special cases, because they are not gotten directly (we have to go to the first //part of it (which in turn may be an attribute) else if (ast2 instanceof Call) { Call c = (Call) ast2; return getColDefinition(c.func); } else if (ast2 instanceof Subscript) { Subscript s = (Subscript) ast2; return getColDefinition(s.value); } else if (always1ForImports) { if (ast2 instanceof Import || ast2 instanceof ImportFrom) { return 1; } } return getClassOrFuncColDefinition(ast2); } public static int getClassOrFuncColDefinition(SimpleNode ast2) { if (ast2 instanceof ClassDef) { ClassDef def = (ClassDef) ast2; return def.name.beginColumn; } if (ast2 instanceof FunctionDef) { FunctionDef def = (FunctionDef) ast2; return def.name.beginColumn; } return ast2.beginColumn; } public static int[] getColLineEnd(SimpleNode v) { return getColLineEnd(v, true); } /** * @param v the token to work with * @return a tuple with [line, col] of the definition of a token */ public static int[] getColLineEnd(SimpleNode v, boolean getOnlyToFirstDot) { int lineEnd = getLineEnd(v); int col = 0; if (v instanceof Import || v instanceof ImportFrom) { return new int[] { lineEnd, -1 }; //col is -1... import is always full line } if (v instanceof Str) { if (lineEnd == getLineDefinition(v)) { String s = ((Str) v).s; col = getColDefinition(v) + s.length(); return new int[] { lineEnd, col }; } else { //it is another line... String s = ((Str) v).s; int i = s.lastIndexOf('\n'); String sub = s.substring(i, s.length()); col = sub.length(); return new int[] { lineEnd, col }; } } col = getEndColFromRepresentation(v, getOnlyToFirstDot); return new int[] { lineEnd, col }; } /** * @param v * @return */ private static int getEndColFromRepresentation(SimpleNode v, boolean getOnlyToFirstDot) { int col; String representationString = getFullRepresentationString(v); if (representationString == null) { return -1; } if (getOnlyToFirstDot) { int i; if ((i = representationString.indexOf('.')) != -1) { representationString = representationString.substring(0, i); } } int colDefinition = getColDefinition(v); if (colDefinition == -1) { return -1; } col = colDefinition + representationString.length(); return col; } public static int getLineEnd(SimpleNode v) { if (v instanceof Expr) { Expr expr = (Expr) v; v = expr.value; } if (v instanceof ImportFrom) { ImportFrom f = (ImportFrom) v; FindLastLineVisitor findLastLineVisitor = new FindLastLineVisitor(); try { f.accept(findLastLineVisitor); SimpleNode lastNode = findLastLineVisitor.getLastNode(); ISpecialStr lastSpecialStr = findLastLineVisitor.getLastSpecialStr(); if (lastSpecialStr != null && lastSpecialStr.toString().equals(")")) { //it was an from xxx import (euheon, utehon) return lastSpecialStr.getBeginLine(); } else { return lastNode.beginLine; } } catch (Exception e) { Log.log(e); } } if (v instanceof Import) { Import f = (Import) v; FindLastLineVisitor findLastLineVisitor = new FindLastLineVisitor(); try { f.accept(findLastLineVisitor); SimpleNode lastNode = findLastLineVisitor.getLastNode(); return lastNode.beginLine; } catch (Exception e) { Log.log(e); } } if (v instanceof Str) { String s = ((Str) v).s; int found = 0; for (int i = 0; i < s.length(); i++) { if (s.charAt(i) == '\n') { found += 1; } } return getLineDefinition(v) + found; } return getLineDefinition(v); } /** * @return the builtin type (if any) for some token (e.g.: '' would return str, 1.0 would return float... */ public static String getBuiltinType(String tok) { if (tok.endsWith("'") || tok.endsWith("\"")) { //ok, we are getting code completion for a string. return "str"; } else if (tok.endsWith("]") && tok.startsWith("[")) { //ok, we are getting code completion for a list. return "list"; } else if (tok.endsWith("}") && tok.startsWith("{")) { //ok, we are getting code completion for a dict. return "dict"; } else if (tok.endsWith(")") && tok.startsWith("(")) { //ok, we are getting code completion for a tuple. return "tuple"; } else { try { Integer.parseInt(tok); return "int"; } catch (Exception e) { //ok, not parsed as int } try { Float.parseFloat(tok); return "float"; } catch (Exception e) { //ok, not parsed as int } } return null; } public static String getNameFromNameTok(NameTokType tok) { return ((NameTok) tok).id; } public static String getNameFromNameTok(NameTok tok) { return tok.id; } /** * Gets all the parts contained in some attribute in the right order (when we visit * some attribute, we have to get that in a backwards fashion, since the attribute * is only determined in the end of the token in the grammar) * * @return a list with the attribute parts in its forward order, and not backward as presented * in the grammar. */ public static List<SimpleNode> getAttributeParts(Attribute node) { ArrayList<SimpleNode> nodes = new ArrayList<SimpleNode>(); nodes.add(node.attr); SimpleNode s = node.value; while (true) { if (s instanceof Attribute) { nodes.add(s); s = ((Attribute) s).value; } else if (s instanceof Call) { nodes.add(s); s = ((Call) s).func; } else { nodes.add(s); break; } } Collections.reverse(nodes); return nodes; } /** * Gets the parent names for a class definition * * @param onlyLastSegment determines whether we should return only the last segment if the name * of the parent resolves to a dotted name. */ public static List<String> getParentNames(ClassDef def, boolean onlyLastSegment) { ArrayList<String> ret = new ArrayList<String>(); for (exprType base : def.bases) { String rep = getFullRepresentationString(base); if (onlyLastSegment) { rep = FullRepIterable.getLastPart(rep); } ret.add(rep); } return ret; } /** * @return true if the node is an import node (and false otherwise). */ public static boolean isImport(SimpleNode ast) { if (ast instanceof Import || ast instanceof ImportFrom) { return true; } return false; } /** * @return true if the node is a comment import node (and false otherwise). */ public static boolean isComment(SimpleNode ast) { if (ast instanceof commentType) { return true; } return false; } public static NameTok getNameForAlias(aliasType t) { if (t.asname != null) { return (NameTok) t.asname; } else { return (NameTok) t.name; } } public static NameTok getNameForRep(aliasType[] names, String representation) { for (aliasType name : names) { NameTok nameForAlias = getNameForAlias(name); String aliasRep = NodeUtils.getRepresentationString(nameForAlias); if (representation.equals(aliasRep)) { return nameForAlias; } } return null; } /** * @param lineNumber the line we want to get the context from (starts at 0) * @param ast the ast that corresponds to our context * @return the full name for the context where we are (in the format Class.method.xxx.xxx) */ public static String getContextName(int lineNumber, SimpleNode ast) { if (ast != null) { EasyASTIteratorVisitor visitor = EasyASTIteratorVisitor.create(ast); Iterator<ASTEntry> classesAndMethodsIterator = visitor.getClassesAndMethodsIterator(); ASTEntry last = null; while (classesAndMethodsIterator.hasNext()) { ASTEntry entry = classesAndMethodsIterator.next(); if (entry.node.beginLine > lineNumber + 1) { //ok, now, let's find out which context actually contains it... break; } last = entry; } while (last != null && last.endLine <= lineNumber) { last = last.parent; } if (last != null) { return getFullMethodName(last); } } return null; } /** * @param ASTEntry last * @return classdef.method_name */ public static String getFullMethodName(ASTEntry last) { StringBuffer buffer = new StringBuffer(); boolean first = true; while (last != null) { String name = last.getName(); buffer.insert(0, name); last = last.parent; if (!first) { buffer.insert(name.length(), "."); } first = false; } return buffer.toString(); } /** * Identifies the context for both source and target line * * @param ASTEntry * ast * @param int sourceLine: the line at which debugger is stopped currently * (starts at 1) * @param int targetLine: the line at which we need to set next (starts at * 0) * @return */ public static boolean isValidContextForSetNext(SimpleNode ast, int sourceLine, int targetLine) { String sourceFunctionName = NodeUtils.getContextName((sourceLine - 1), ast); String targetFunctionName = NodeUtils.getContextName(targetLine, ast); if (compareMethodName(sourceFunctionName, targetFunctionName)) { ASTEntry sourceAST = NodeUtils.getLoopContextName(sourceLine, ast); ASTEntry targetAST = NodeUtils.getLoopContextName(targetLine + 1, ast); if (targetAST == null) { return true; // Target line is not inside some loop } if (isValidElseBlock(sourceAST, targetAST, sourceLine, targetLine)) { return true; // Debug pointer can be set inside else block of // for..else/while..else } if (sourceAST == null && targetAST != null) { return false; // Source is outside loop and target is inside // loop } if (sourceAST != null && targetAST != null) { // Both Source and Target is inside some loop if (sourceAST.equals(targetAST)) { return isValidInterLoopContext(sourceLine, targetLine, sourceAST, targetAST); } else { ASTEntry last = sourceAST; boolean retVal = false; while (last != null) { ASTEntry parentAST = last.parent; if (parentAST != null && parentAST.equals(targetAST)) { retVal = true; break; } last = parentAST; } return retVal; } } return true; } else { return false; } } /** * Compare name of two methods. return true if either both methods are same * or global context * * @param sourceMethodName * @param targetMethodName * @return */ public static boolean compareMethodName(String sourceMethodName, String targetMethodName) { if ((sourceMethodName == null && targetMethodName == null)) return true; if ((sourceMethodName != null) && sourceMethodName.equals(targetMethodName)) return true; return false; } /** * Identifies the for/while/try..except/try..finally and with for a provided * line number. * * @param lineNumber * the line we want to get the loop context (starts at 1) * @param ast * @return */ public static ASTEntry getLoopContextName(int lineNumber, SimpleNode ast) { ASTEntry loopContext = null; if (ast != null) { int highestBeginLine = 0; ArrayList<ASTEntry> contextBlockList = new ArrayList<ASTEntry>(); EasyASTIteratorWithLoop visitor = EasyASTIteratorWithLoop.create(ast); Iterator<ASTEntry> blockIterator = visitor.getIterators(); while (blockIterator.hasNext()) { ASTEntry entry = blockIterator.next(); if ((entry.node.beginLine) < lineNumber && entry.endLine >= lineNumber) { contextBlockList.add(entry); if (entry.node.beginLine > highestBeginLine) { highestBeginLine = entry.node.beginLine; } } } Iterator<ASTEntry> contextBlockListIterator = contextBlockList.iterator(); while (contextBlockListIterator.hasNext()) { ASTEntry astEntry = contextBlockListIterator.next(); if (astEntry.node.beginLine == highestBeginLine) { loopContext = astEntry; } } } return loopContext; } /** * Set Next into else block of for..else/while..else is also allowed even if * current pointer is outside for..else/while..else but current pointer * context should be immediate parent of target for..else/while..else * * @param sourceAST * @param targetAST * @param sourceLine * : the line at which debugger is stopped currently (starts at * 1) * @param targetLine * : the line at which we need to set next (starts at 0) * @return */ public static boolean isValidElseBlock(ASTEntry sourceAST, ASTEntry targetAST, int sourceLine, int targetLine) { boolean retval = false; if (targetAST.node instanceof For || targetAST.node instanceof While) { int targetElseBeginLine = getElseBeginLine(targetAST); if (targetElseBeginLine > 0 && targetLine + 1 > targetElseBeginLine) { if ((targetAST.parent == null || targetAST.parent.node instanceof FunctionDef) && sourceAST == null) { retval = true; } else if (targetAST.parent != null && targetAST.parent.equals(sourceAST)) { int sourceElseBeginLine = getElseBeginLine(sourceAST); if (sourceLine > sourceElseBeginLine) { retval = false; } else { retval = true; } } } } return retval; } /** * Identifies the begin line of else block for for..else/while..else and * first exception begin line for try..except..else block * * @param astEntry * @return */ public static int getElseBeginLine(ASTEntry astEntry) { int beginLine = 0; if (astEntry.node instanceof TryExcept && ((TryExcept) astEntry.node).handlers.length > 0) { beginLine = ((TryExcept) astEntry.node).handlers[0].beginLine; } else if (astEntry.node instanceof For && ((For) astEntry.node).orelse != null) { beginLine = ((For) astEntry.node).orelse.beginLine; } else if (astEntry.node instanceof While && ((While) astEntry.node).orelse != null) { beginLine = ((While) astEntry.node).orelse.beginLine; } return beginLine; } /** * * * @param sourceLine * @param targetLine * @param sourceAST * @param targetAST * @return */ public static boolean isValidInterLoopContext(int sourceLine, int targetLine, ASTEntry sourceAST, ASTEntry targetAST) { boolean retval = true; if (sourceAST.node instanceof TryExcept && targetAST.node instanceof TryExcept && (!isValidTryExceptContext(sourceAST, targetAST, sourceLine, targetLine))) { retval = false; } else if (sourceAST.node instanceof For && targetAST.node instanceof For && (!isValidForContext(sourceAST, targetAST, sourceLine, targetLine))) { retval = false; } else if (sourceAST.node instanceof While && targetAST.node instanceof While && (!isValidWhileContext(sourceAST, targetAST, sourceLine, targetLine))) { retval = false; } return retval; } /** * Identifies the valid set next target inside Try..except..else block * * @param sourceAST * @param targetAST * @param sourceLine * : the line at which debugger is stopped currently (starts at * 1) * @param targetLine * : the line at which we need to set next (starts at 0) * @return */ public static boolean isValidTryExceptContext(ASTEntry sourceAST, ASTEntry targetAST, int sourceLine, int targetLine) { excepthandlerType[] exceptionHandlers = ((TryExcept) sourceAST.node).handlers; if (((TryExcept) sourceAST.node).specialsAfter != null) { // Pointer can't be set on comment(s) in try block List<Object> specialList = ((TryExcept) sourceAST.node).specialsAfter; for (Object obj : specialList) { if (obj instanceof commentType && targetLine + 1 == ((commentType) obj).beginLine) { return false; } } } for (int i = 0; i < exceptionHandlers.length; i++) { excepthandlerType exceptionHandler = exceptionHandlers[i]; // Pointer can't be set on except... statement(s) if (targetLine + 1 == exceptionHandler.beginLine) { return false; } } // Pointer can't be moved inside try block from except or else block if (exceptionHandlers.length > 0) { int exceptionBeginLine = exceptionHandlers[0].beginLine; if (targetLine + 1 > ((TryExcept) sourceAST.node).beginLine && targetLine + 1 < exceptionBeginLine && sourceLine >= exceptionBeginLine) { return false; } } return true; } /** * Identifies the valid set next target inside while..else block * * @param sourceAST * @param targetAST * @param sourceLine * : the line at which debugger is stopped currently (starts at * 1) * @param targetLine * : the line at which we need to set next (starts at 0) * @return */ public static boolean isValidWhileContext(ASTEntry sourceAST, ASTEntry targetAST, int sourceLine, int targetLine) { // Pointer can't be moved inside while block from else block if (((While) sourceAST.node).orelse != null) { int elseBeginLine = ((While) sourceAST.node).orelse.beginLine; if (targetLine + 1 > ((While) sourceAST.node).beginLine && targetLine + 1 < elseBeginLine && sourceLine >= elseBeginLine) { return false; } } return true; } /** * Identifies the valid set next target inside for..else block * * @param sourceAST * @param targetAST * @param sourceLine * : the line at which debugger is stopped currently (starts at * 1) * @param targetLine * : the line at which we need to set next (starts at 0) * @return */ public static boolean isValidForContext(ASTEntry sourceAST, ASTEntry targetAST, int sourceLine, int targetLine) { // Pointer can't be moved inside for block from else block if (((For) sourceAST.node).orelse != null) { int elseBeginLine = ((For) sourceAST.node).orelse.beginLine; if (targetLine + 1 > ((For) sourceAST.node).beginLine && targetLine + 1 < elseBeginLine && sourceLine >= elseBeginLine) { return false; } } return true; } protected static final String[] strTypes = new String[] { "'''", "\"\"\"", "'", "\"" }; public static String getStringToPrint(Str node) { StringBuffer buffer = new StringBuffer(); if (node.unicode) { buffer.append("u"); } if (node.binary) { buffer.append("b"); } if (node.raw) { buffer.append("r"); } final String s = strTypes[node.type - 1]; buffer.append(s); buffer.append(node.s); buffer.append(s); return buffer.toString(); } /** * @param node the if node that we want to check. * @return null if the passed node is not */ public static boolean isIfMAinNode(If node) { if (node.test instanceof Compare) { Compare compareNode = (Compare) node.test; // handcrafted structure walking if (compareNode.left instanceof Name && ((Name) compareNode.left).id.equals("__name__") && compareNode.ops != null && compareNode.ops.length == 1 && compareNode.ops[0] == Compare.Eq) { if (compareNode.comparators != null && compareNode.comparators.length == 1 && compareNode.comparators[0] instanceof Str && ((Str) compareNode.comparators[0]).s.equals("__main__")) { return true; } } } return false; } /** * @return true if the given name is a valid python name. */ public static boolean isValidNameRepresentation(String rep) { if (rep == null) { return false; } if ("pass".equals(rep) || rep.startsWith("!<") || rep.indexOf(' ') != -1) { //Name generated during the parsing (in AbstractPythonGrammar) return false; } return true; } /** * Creates an attribute from the passed string * * @param attrString: A string as 'a.b.c' or 'self.b' (at least one dot must be in the string) or self.xx() * Note that the call is only accepted as the last part. * @return an Attribute representing the string. */ public static exprType makeAttribute(String attrString) { List<String> dotSplit = StringUtils.dotSplit(attrString); Assert.isTrue(dotSplit.size() > 1); exprType first = null; Attribute last = null; Attribute attr = null; for (int i = dotSplit.size() - 1; i > 0; i--) { Call call = null; String part = dotSplit.get(i); if (part.endsWith("()")) { if (i == dotSplit.size() - 1) { part = part.substring(0, part.length() - 2); call = new Call(null, new exprType[0], new keywordType[0], null, null); first = call; } else { throw new RuntimeException("Call only accepted in the last part."); } } attr = new Attribute(null, new NameTok(part, NameTok.Attrib), Attribute.Load); if (call != null) { call.func = attr; } if (last != null) { last.value = attr; } last = attr; if (first == null) { first = last; } } String lastPart = dotSplit.get(0); if (lastPart.endsWith("()")) { last.value = new Call(new Name(lastPart.substring(0, lastPart.length() - 2), Name.Load, false), null, null, null, null); } else { last.value = new Name(lastPart, Name.Load, false); } return first; } /** * @return the body of the passed node (if it doesn't have a body, an empty array is returned). */ public static stmtType[] getBody(SimpleNode node) { if (node instanceof Module) { Module module = (Module) node; return module.body; } if (node instanceof ClassDef) { ClassDef module = (ClassDef) node; return module.body; } if (node instanceof FunctionDef) { FunctionDef module = (FunctionDef) node; return module.body; } if (node instanceof excepthandlerType) { excepthandlerType module = (excepthandlerType) node; return module.body; } if (node instanceof For) { For module = (For) node; return module.body; } if (node instanceof If) { If module = (If) node; return module.body; } if (node instanceof Suite) { Suite module = (Suite) node; return module.body; } if (node instanceof suiteType) { suiteType module = (suiteType) node; return module.body; } if (node instanceof TryExcept) { TryExcept module = (TryExcept) node; return module.body; } if (node instanceof TryFinally) { TryFinally module = (TryFinally) node; return module.body; } if (node instanceof While) { While module = (While) node; return module.body; } if (node instanceof With) { With module = (With) node; return module.body.body; } return new stmtType[0]; } /** * @param node This is the node where we should start looking (usually the Module) * @param path This is the path for which we want an item in the given node. * E.g.: If we want to find a method testFoo in a class TestCase, we'de pass TestCase.testFoo as the path. * */ public static SimpleNode getNodeFromPath(SimpleNode node, String path) { SimpleNode leafTestNode = null; SimpleNode last = node; for (String s : StringUtils.dotSplit(path)) { stmtType found = null; for (stmtType n : NodeUtils.getBody(last)) { if (s.equals(NodeUtils.getRepresentationString(n))) { found = n; last = n; break; } } if (found == null) { leafTestNode = null; break; } else { leafTestNode = found; } } return leafTestNode; } /** * Finds the statement that contains the given node. * * @param source: this is the ast that contains the body with multiple statements. * @param ast: This is the ast for which we want the statement. */ public static stmtType findStmtForNode(SimpleNode source, final SimpleNode ast) { VisitorBase v = new VisitorBase() { private stmtType lastStmtFound; @Override protected Object unhandled_node(SimpleNode node) throws Exception { if (node instanceof stmtType) { lastStmtFound = (stmtType) node; } if (node.beginColumn == ast.beginColumn && node.beginLine == ast.beginLine && node.getClass() == ast.getClass() && node.toString().equals(ast.toString())) { throw new StopVisitingException(lastStmtFound); } return null; } @Override public void traverse(SimpleNode node) throws Exception { node.traverse(this); } }; stmtType[] body = getBody(source); stmtType last = null; for (stmtType stmtType : body) { if (stmtType.beginLine > ast.beginLine) { //already passed the possible statement, check the last one (which is the last statement that //has a beginLine <= ast.beginLine) and return even if we didn't find it, as we already passed the //target line. if (last != null) { return checkNode(v, last); } } if (stmtType.beginLine == ast.beginLine) { //If we have a case in the same line, we must also check it. Don't mark it as last in this case as we've //already checked it. stmtType n = checkNode(v, stmtType); if (n != null) { return n; } } else { last = stmtType; } } return null; } private static stmtType checkNode(VisitorBase v, stmtType last) { try { last.accept(v); } catch (StopVisitingException e) { if (e.lastStmtFound != null) { //it could be that we found a statement inside this statement. return e.lastStmtFound; } else { //Ok, there were no more statements there, so, just go on with the last we received. return last; } } catch (Exception e) { Log.log(e); } //Not found! return null; } public static int getOffset(IDocument doc, SimpleNode node) { int nodeOffsetBegin = PySelection.getAbsoluteCursorOffset(doc, node.beginLine - 1, node.beginColumn - 1); return nodeOffsetBegin; } }