# A handy translator that converts control flow into the appropriate # llvm_cbuilder constructs from numba.functions import _get_ast, fix_ast_lineno import inspect, functools, ast import logging logger = logging.getLogger(__name__) def translate(func): # TODO use meta package wrapper = functools.wraps(func) caller_frame = inspect.currentframe().f_back tree = _get_ast(func) tree = ast.Module(body=tree.body) tree = ExpandControlFlow().visit(tree) fix_ast_lineno(tree) # prepare locals for execution local_dict = locals() local_dict.update(caller_frame.f_locals) local_dict.update(caller_frame.f_globals) try: compiled = compile(tree, '', 'exec') return eval(compiled) except Exception as e: logger.debug(ast.dump(tree)) from ArminRonacher import codegen # uses Armin Ronacher's codegen to debug # http://dev.pocoo.org/hg/sandbox/file/852a1248c8eb/ast/codegen.py logger.debug(codegen.to_source(tree)) raise _if_else_template = ''' with self.ifelse(__CONDITION__) as _ifelse_: with _ifelse_.then(): __THEN__ with _ifelse_.otherwise(): __OTHERWISE__ ''' _while_template = ''' with self.loop() as _loop_: with _loop_.condition() as _setcond_: _setcond_(__CONDITION__) with _loop_.body(): __BODY__ ''' _for_range_template = ''' with self.for_range(*__ARGS__) as (_loop_, __ITER__): __BODY__ ''' _return_template = 'self.ret(__RETURN__)' _const_int_template = 'self.constant(C.int, __VALUE__)' _const_long_template = 'self.constant(C.long, __VALUE__)' _const_float_template = 'self.constant(C.double, __VALUE__)' def load_template(string): ''' Since ast.parse() returns a ast.Module node, it is more useful to trim the Module and get to the first item of body ''' tree = ast.parse(string) # return a Module assert isinstance(tree, ast.Module) return tree.body[0] # get the first item of body class ExpandControlFlow(ast.NodeTransformer): ''' Expand control flow contructs. These are the most tedious thing to do in llvm_cbuilder. ''' ## Use breadcumb to track parent nodes # def __init__(self): # self.breadcumb = [] # # def visit(self, node): # self.breadcumb.append(node) # try: # return super(ExpandControlFlow, self).visit(node) # finally: # self.breadcumb.pop() # # @property # def parent(self): # return self.breadcumb[-2] def visit_If(self, node): mapping = { '__CONDITION__' : node.test, '__THEN__' : node.body, '__OTHERWISE__' : node.orelse, } ifelse = load_template(_if_else_template) ifelse = MacroExpander(mapping).visit(ifelse) newnode = self.generic_visit(ifelse) return newnode def visit_While(self, node): mapping = { '__CONDITION__' : node.test, '__BODY__' : node.body, } whileloop = load_template(_while_template) whileloop = MacroExpander(mapping).visit(whileloop) newnode = self.generic_visit(whileloop) return newnode def visit_For(self, node): try: if node.iter.func.id not in ['range', 'xrange']: return node except AttributeError: return node mapping = { '__ITER__' : node.target, '__BODY__' : node.body, '__ARGS__' : ast.Tuple(elts=node.iter.args, ctx=ast.Load()), } forloop = load_template(_for_range_template) forloop = MacroExpander(mapping).visit(forloop) newnode = self.generic_visit(forloop) return newnode def visit_Return(self, node): mapping = {'__RETURN__' : node.value} ret = load_template(_return_template) repl = MacroExpander(mapping).visit(ret) return repl def visit_Num(self, node): '''convert immediate values ''' typemap = { int : _const_int_template, long : _const_long_template, # TODO: disable long for py3 float : _const_float_template, } template = load_template(typemap[type(node.n)]) mapping = { '__VALUE__' : node, } constant = MacroExpander(mapping).visit(template).value newnode = constant return newnode class MacroExpander(ast.NodeTransformer): def __init__(self, mapping): self.mapping = mapping def visit_With(self, node): ''' Expand X in the following: with blah: X Nothing should go before or after X. X must be a list of nodes. ''' if (len(node.body)==1 # the body of and isinstance(node.body[0], ast.Expr) and isinstance(node.body[0].value, ast.Name)): try: repl = self.mapping.pop(node.body[0].value.id) except KeyError: pass else: old = node.body[0] node.body = repl return self.generic_visit(node) # recursively apply expand all macros def visit_Name(self, node): ''' Expand all Name node to simple value ''' try: repl = self.mapping.pop(node.id) except KeyError: pass else: if repl is not None and not isinstance(repl, list): return repl return node