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