def find_dependencies(root): """ Finds all dependencies in root object based on symbol table. Consider a dependecy as containing a source and a destination. Cases (nodes) to account for: 1) Assign -check for the Name being assigned 2) Name being invoked -here it makes sense to search for name 3) Attribute e.g. pdb.set_trace(), set_trace is an attribute of pdb -see check dependency 4) Import These are needed when building a symbols table 5) ImportFrom ibid Gotchas: 1) a name being stored i.e. a name collisions that makes it seem like a dependency exists 2) x = y = z = 3 3) Attribute chains can be arbitrarily long x.y.z, or x().y().z(), or some combination thereof There are a lot of cases to be handled Arguments: root:- the root of the AST being analyzed stored as (2-tuple) with (root node, array of children ) """ symbol_table = create_symbol_table(root) names = [] #Set the depth of the root node set_depth(root, 0) #Stack of nodes to visit stack = Stack(root) #List of (src, dest) of dependencies dependency_table = DTable(symbol_table=symbol_table) for node, children, ntype in stack: stack.check_and_push_scope() #A Name is being loaded, therefore if ntype == "Name" and is_load(children): """ """ dependency_table.append( (stack.scopes, node)) elif ntype == "Assign": #TODO need to add assignments and then revoke them #for child in children: #print children pass elif ntype == "Attribute": #TODO: attribute chains can be arbitrarily long #dep_dest = "{}.{}".format(node.value.id, node.attr) #print "{} => {}".format(scopes_to_str(scopes), dep_dest) #TODO: Can't just do dependency_table.append( (scopes, node)) #since the unique_id function won't match the create the dep string like #{node.value.id}.{node.attr}. #Either generalize unique_id or something else. #Don't add children continue set_lineno(node, children) #Add children to stack #This musn't always be performed for child in children[::-1]: set_depth(child, node.depth + 1) stack.append(child) print "dependency table is " print dependency_table
def create_symbol_table(root): """ Creates a symbols table that maps each symbol to the scope within which it occurs. The symbol table has to be created in its entirety first, since entities (e.g. functions, classes) can be referenced before being defined. Similar to find_dependencies in terms of traversing the AST. TODO: refactor common stuff into separate function The data structure used is a hashtable where names are mapped to list of scopes, i.e. a hashlist. The scope can be precisely defined in terms of lineno range. The alternative is to define as scope as, e.g. <module name>.<function name> but this can lead to ambiguity since the functions etc. can be redefined. Also the lineno approach relies on the beginning lines of siblings which can lead to a larger range than actually is due to whitespaces. This gets tricky because functions can be used before they are defined, but not variables. """ set_depth(root, 0) #Initialize the stack, with the AST root stack = Stack(root) #the symbol table maps the name to the scope. #Any node can belong to multiple scopes, therefore this #is a list of scope symbol_table = STable() #this represents objects imported from #other modules other_modules = {} for node, children, ntype in stack: if ntype == "Import": #Import object has names prop which #is an array of names for name in node.names: #name can be the name or an alias name_val = name.asname or name.name #insert in symbol_table symbol_table[name_val] = () elif ntype == "ImportFrom": if node.names[0].name == '*': try: imp_mod = importlib.import_module(node.module) #Add all names in imported module, except those #starting with '_' for name in dir(imp_mod): if name[0] != '_': symbol_table[name] = stack_top(scopes) except ImportError: print "Error: local system does not have {}. Skipping!".format(node.module) pass else: #TODO: store node.module for name in node.names: #TODO: store name.name even if name.asname defined name_val = name.asname or name.name symbol_table[name_val] = stack.get_scopes(src_module=node.module) elif ntype == "ClassDef" or ntype == "FunctionDef": symbol_table[node.name] = stack.get_scopes() #NOTE: if a name is being loaded then it already exists and doesn't need #to be added to symbol_table elif ntype == "Name" and not is_load(children) and not has_global(stack.scope_tail(), node.id): symbol_table[node.id] = stack.get_scopes() elif ntype == "arguments": if node.vararg: symbol_table[node.vararg] = stack.get_scopes() if node.kwarg: symbol_table[node.kwarg] = stack.get_scopes() elif ntype == "Global": #add a list global vars on node on the top of #the stack #nonlocal could be handled in similar way set_globals(scopes[-1], node.names) #set lineno property of children nodes set_lineno(node, children) for child in children[::-1]: #set depth of child set_depth(child, node.depth + 1) #Add children to stack stack.append(child) #Add any new scopes #Need to do it here since scoping_nodes are defined in their parent scope stack.check_and_push_scope() print "Symbol table is " print symbol_table return symbol_table