def abstract_code_from_function(func): ''' Converts the body of the function to abstract code Parameters ---------- func : function, str or ast.FunctionDef The function object to convert. Note that the arguments to the function are ignored. Returns ------- func : AbstractCodeFunction The corresponding abstract code function Raises ------ SyntaxError If unsupported features are used such as if statements or indexing. ''' if callable(func): code = deindent(inspect.getsource(func)) funcnode = ast.parse(code, mode='exec').body[0] elif isinstance(func, str): funcnode = ast.parse(func, mode='exec').body[0] elif func.__class__ is ast.FunctionDef: funcnode = func else: raise TypeError("Unsupported function type") if funcnode.args.vararg is not None: raise SyntaxError("No support for variable number of arguments") if funcnode.args.kwarg is not None: raise SyntaxError("No support for arbitrary keyword arguments") if len(funcnode.args.defaults): raise SyntaxError("No support for default values in functions") nodes = funcnode.body nr = NodeRenderer() lines = [] return_expr = None for node in nodes: if node.__class__ is ast.Return: return_expr = nr.render_node(node.value) break else: lines.append(nr.render_node(node)) abstract_code = '\n'.join(lines) try: # Python 2 args = [arg.id for arg in funcnode.args.args] except AttributeError: # Python 3 args = [arg.arg for arg in funcnode.args.args] name = funcnode.name return AbstractCodeFunction(name, args, abstract_code, return_expr)
def substitute_abstract_code_functions(code, funcs): ''' Performs inline substitution of all the functions in the code Parameters ---------- code : str The abstract code to make inline substitutions into. funcs : list, dict or set of AbstractCodeFunction The function substitutions to use, note in the case of a dict, the keys are ignored and the function name is used. Returns ------- code : str The code with inline substitutions performed. ''' if isinstance(funcs, (list, set)): newfuncs = dict() for f in funcs: newfuncs[f.name] = f funcs = newfuncs code = deindent(code) lines = ast.parse(code, mode='exec').body # This is a slightly nasty hack, but basically we just check by looking at # the existing identifiers how many inline operations have already been # performed by previous calls to this function ids = get_identifiers(code) funcstarts = {} for func in funcs.values(): subids = set([id for id in ids if id.startswith('_inline_'+func.name+'_')]) subids = set([id.replace('_inline_'+func.name+'_', '') for id in subids]) alli = [] for subid in subids: p = subid.find('_') if p>0: subid = subid[:p] i = int(subid) alli.append(i) if len(alli)==0: i = 0 else: i = max(alli)+1 funcstarts[func.name] = i # Now we rewrite all the lines, replacing each line with a sequence of # lines performing the inlining newlines = [] for line in lines: for func in funcs.values(): rw = FunctionRewriter(func, funcstarts[func.name]) line = rw.visit(line) newlines.extend(rw.pre) funcstarts[func.name] = rw.numcalls newlines.append(line) # Now we render to a code string nr = NodeRenderer() newcode = '\n'.join(nr.render_node(line) for line in newlines) # We recurse until no changes in the code to ensure that all functions # are expanded if one function refers to another, etc. if newcode==code: return newcode else: return substitute_abstract_code_functions(newcode, funcs)