%PDF- %PDF-
Direktori : /lib/python3/dist-packages/pythran/ |
Current File : //lib/python3/dist-packages/pythran/backend.py |
''' This module contains all pythran backends. * Cxx dumps the AST into C++ code * Python dumps the AST into Python code ''' from pythran.analyses import LocalNodeDeclarations, GlobalDeclarations, Scope from pythran.analyses import YieldPoints, IsAssigned, ASTMatcher, AST_any from pythran.analyses import RangeValues, PureExpressions, Dependencies from pythran.analyses import Immediates, Ancestors from pythran.cxxgen import Template, Include, Namespace, CompilationUnit from pythran.cxxgen import Statement, Block, AnnotatedStatement, Typedef, Label from pythran.cxxgen import Value, FunctionDeclaration, EmptyStatement, Nop from pythran.cxxgen import FunctionBody, Line, ReturnStatement, Struct, Assign from pythran.cxxgen import For, While, TryExcept, ExceptHandler, If, AutoFor from pythran.openmp import OMPDirective from pythran.passmanager import Backend from pythran.syntax import PythranSyntaxError from pythran.tables import operator_to_lambda, update_operator_to_lambda from pythran.tables import pythran_ward from pythran.types.conversion import PYTYPE_TO_CTYPE_TABLE, TYPE_TO_SUFFIX from pythran.types.types import Types from pythran.utils import attr_to_path, pushpop, cxxid, isstr, isnum from pythran.utils import isextslice, ispowi from pythran import metadata, unparse from math import isnan, isinf import gast as ast import os from functools import reduce import io class Python(Backend): ''' Produces a Python representation of the AST. >>> import gast as ast, pythran.passmanager as passmanager >>> node = ast.parse("print('hello world')") >>> pm = passmanager.PassManager('test') >>> print(pm.dump(Python, node)) print('hello world') ''' def __init__(self): self.result = '' super(Python, self).__init__() def visit(self, node): output = io.StringIO() unparse.Unparser(node, output) self.result = output.getvalue() def templatize(node, types, default_types=None): if not default_types: default_types = [None] * len(types) if types: return Template( ["typename {0} {1}".format(t, "= {0}".format(d) if d else "") for t, d in zip(types, default_types)], node) else: return node def cxx_loop(visit): """ Decorator for loop node (For and While) to handle "else" branching. Decorated node will save flags for a goto statement used instead of usual break and add this flag at the end of the else statements. Examples -------- >> for i in range(12): >> if i == 5: >> break >> else: >> ... some code ... Becomes >> for(type i : range(12)) >> if(i==5) >> goto __no_breaking0; >> ... some code ... >> __no_breaking0; """ def loop_visitor(self, node): """ New decorate function. It push the breaking flag, run the visitor and add "else" statements. """ if not node.orelse: with pushpop(self.break_handlers, None): res = visit(self, node) return res break_handler = "__no_breaking{0}".format(id(node)) with pushpop(self.break_handlers, break_handler): res = visit(self, node) # handle the body of the for loop orelse = [self.visit(stmt) for stmt in node.orelse] if break_handler in self.used_break: orelse_label = [Label(break_handler)] else: orelse_label = [] return Block([res] + orelse + orelse_label) return loop_visitor class CachedTypeVisitor: def __init__(self, other=None): if other is None: self.cache = dict() self.rcache = dict() self.mapping = dict() else: self.cache = other.cache.copy() self.rcache = other.rcache.copy() self.mapping = other.mapping.copy() def __call__(self, node): if node not in self.mapping: t = node.generate(self) if node not in self.mapping: if t in self.rcache: self.mapping[node] = self.mapping[self.rcache[t]] self.cache[node] = self.cache[self.rcache[t]] else: self.rcache[t] = node self.mapping[node] = len(self.mapping) self.cache[node] = t return "__type{0}".format(self.mapping[node]) def typedefs(self): kv = sorted(self.mapping.items(), key=lambda x: x[1]) L = list() visited = set() # the same value must not be typedefed twice for k, v in kv: if v not in visited: typename = "__type" + str(v) L.append(Typedef(Value(self.cache[k], typename))) visited.add(v) return L def make_default(d): return "= {0}".format(d) if d else "" def make_function_declaration(self, node, rtype, name, ftypes, fargs, defaults=None, attributes=None): if defaults is None: defaults = [None] * len(ftypes) if attributes is None: attributes = [] arguments = list() first_default = len(node.args.args) - len(node.args.defaults) for i, (t, a, d) in enumerate(zip(ftypes, fargs, defaults)): # because universal reference and default don't get on well if isinstance(self, CxxGenerator) or i >= first_default: rvalue_ref = "" else: rvalue_ref = "&&" argument = Value(t + rvalue_ref, "{0}{1}".format(a, make_default(d))) arguments.append(argument) return FunctionDeclaration(Value(rtype, name), arguments, *attributes) def make_const_function_declaration(self, node, rtype, name, ftypes, fargs, defaults=None): return make_function_declaration(self, node, rtype, name, ftypes, fargs, defaults, ["const"]) class CxxFunction(ast.NodeVisitor): ''' Attributes ---------- ldecls : {str} set of local declarations. break_handler : [str] It contains flags for goto statements to jump on break in case of orelse statement in loop. None means there are no orelse statement so no jump are requiered. (else in loop means : don't execute if loop is terminated with a break) ''' def __init__(self, parent): """ Basic initialiser gathering analysis informations. """ self.parent = parent self.break_handlers = [] self.used_break = set() self.ldecls = None self.openmp_deps = set() def __getattr__(self, attr): return getattr(self.parent, attr) # local declaration processing def process_locals(self, node, node_visited, *skipped): """ Declare variable local to node and insert declaration before. Not possible for function yielding values. """ local_vars = self.scope[node].difference(skipped) local_vars = local_vars.difference(self.openmp_deps) if not local_vars: return node_visited # no processing locals_visited = [] for varname in local_vars: vartype = self.typeof(varname) decl = Statement("{} {}".format(vartype, varname)) locals_visited.append(decl) self.ldecls.difference_update(local_vars) return Block(locals_visited + [node_visited]) def visit_OMPDirective(self, node): self.openmp_deps.update(d.id for d in node.private_deps) self.openmp_deps.update(d.id for d in node.shared_deps) def visit(self, node): metadata.visit(self, node) return super(CxxFunction, self).visit(node) def process_omp_attachements(self, node, stmt, index=None): """ Add OpenMP pragma on the correct stmt in the correct order. stmt may be a list. On this case, index have to be specify to add OpenMP on the correct statement. """ omp_directives = metadata.get(node, OMPDirective) if omp_directives: directives = list() for directive in omp_directives: directive.deps = [self.visit(dep) for dep in directive.deps] directives.append(directive) if index is None: stmt = AnnotatedStatement(stmt, directives) else: stmt[index] = AnnotatedStatement(stmt[index], directives) return stmt def typeof(self, node): if isinstance(node, str): return self.typeof(self.local_names[node]) else: return self.types[node].generate(self.lctx) def prepare_functiondef_context(self, node): # prepare context and visit function body fargs = node.args.args formal_args = [cxxid(arg.id) for arg in fargs] formal_types = ["argument_type" + str(i) for i in range(len(fargs))] local_decls = set(self.gather(LocalNodeDeclarations, node)) self.local_names = {sym.id: sym for sym in local_decls} self.local_names.update({arg.id: arg for arg in fargs}) self.lctx = CachedTypeVisitor() self.ldecls = {n.id for n in local_decls} body = [self.visit(stmt) for stmt in node.body] return body, formal_types, formal_args def prepare_types(self, node): # compute arg dump dflt_argv = ( [None] * (len(node.args.args) - len(node.args.defaults)) + [self.visit(n) for n in node.args.defaults]) dflt_argt = ( [None] * (len(node.args.args) - len(node.args.defaults)) + [self.types[n] for n in node.args.defaults]) # compute type dump result_type = self.types[node][0] callable_type = Typedef(Value("void", "callable")) pure_type = (Typedef(Value("void", "pure")) if node in self.pure_expressions else EmptyStatement()) return dflt_argv, dflt_argt, result_type, callable_type, pure_type # stmt def visit_FunctionDef(self, node): self.fname = cxxid(node.name) tmp = self.prepare_functiondef_context(node) operator_body, formal_types, formal_args = tmp tmp = self.prepare_types(node) dflt_argv, dflt_argt, result_type, callable_type, pure_type = tmp # a function has a call operator to be called # and a default constructor to create instances fscope = "type{0}::".format("<{0}>".format(", ".join(formal_types)) if formal_types else "") ffscope = "{0}::{1}".format(self.fname, fscope) operator_declaration = [ templatize( make_const_function_declaration( self, node, "typename {0}result_type".format(fscope), "operator()", formal_types, formal_args, dflt_argv), formal_types, dflt_argt), EmptyStatement() ] operator_signature = make_const_function_declaration( self, node, "typename {0}result_type".format(ffscope), "{0}::operator()".format(self.fname), formal_types, formal_args) ctx = CachedTypeVisitor(self.lctx) operator_local_declarations = ( [Statement("{0} {1}".format( self.types[self.local_names[k]].generate(ctx), cxxid(k))) for k in self.ldecls] ) dependent_typedefs = ctx.typedefs() operator_definition = FunctionBody( templatize(operator_signature, formal_types), Block(dependent_typedefs + operator_local_declarations + operator_body) ) ctx = CachedTypeVisitor() extra_typedefs = ( [Typedef(Value(t.generate(ctx), t.name)) for t in self.types[node][1]] + [Typedef(Value( result_type.generate(ctx), "result_type"))] ) extra_typedefs = ctx.typedefs() + extra_typedefs return_declaration = [ templatize( Struct("type", extra_typedefs), formal_types, dflt_argt ) ] topstruct = Struct(self.fname, [callable_type, pure_type] + return_declaration + operator_declaration) return [topstruct], [operator_definition] def visit_Return(self, node): value = self.visit(node.value) if metadata.get(node, metadata.StaticReturn): # don't rely on auto because we want to make sure there's no # conversion each time we return # this happens for variant because the variant param # order may differ from the init order (because of the way we # do type inference rtype = "typename {}::type::result_type".format(self.fname) stmt = Block([Assign("static %s tmp_global" % rtype, value), ReturnStatement("tmp_global")]) else: stmt = ReturnStatement(value) return self.process_omp_attachements(node, stmt) def visit_Delete(self, _): return Nop() # nothing to do in there def visit_Assign(self, node): """ Create Assign node for final Cxx representation. It tries to handle multi assignment like: >> a = b = c = 2 If only one local variable is assigned, typing is added: >> int a = 2; TODO: Handle case of multi-assignement for some local variables. Finally, process OpenMP clause like #pragma omp atomic """ if not all(isinstance(n, (ast.Name, ast.Subscript)) for n in node.targets): raise PythranSyntaxError( "Must assign to an identifier or a subscript", node) value = self.visit(node.value) targets = [self.visit(t) for t in node.targets] alltargets = "= ".join(targets) islocal = (len(targets) == 1 and isinstance(node.targets[0], ast.Name) and node.targets[0].id in self.scope[node] and node.targets[0].id not in self.openmp_deps) if islocal: # remove this decls from local decls self.ldecls.difference_update(t.id for t in node.targets) # add a local declaration if self.types[node.targets[0]].iscombined(): alltargets = '{} {}'.format(self.typeof(node.targets[0]), alltargets) elif isinstance(self.types[node.targets[0]], self.types.builder.Assignable): alltargets = '{} {}'.format( self.types.builder.AssignableNoEscape( self.types.builder.NamedType( 'decltype({})'.format(value))), alltargets) else: assert isinstance(self.types[node.targets[0]], self.types.builder.Lazy) alltargets = '{} {}'.format( self.types.builder.Lazy( self.types.builder.NamedType( 'decltype({})'.format(value))), alltargets) stmt = Assign(alltargets, value) return self.process_omp_attachements(node, stmt) def visit_AugAssign(self, node): value = self.visit(node.value) target = self.visit(node.target) op = update_operator_to_lambda[type(node.op)] stmt = Statement(op(target, value)[1:-1]) # strip spurious parenthesis return self.process_omp_attachements(node, stmt) def visit_Print(self, node): values = [self.visit(n) for n in node.values] stmt = Statement("pythonic::builtins::print{0}({1})".format( "" if node.nl else "_nonl", ", ".join(values)) ) return self.process_omp_attachements(node, stmt) def is_in_collapse(self, loop, node): for ancestor in reversed(self.ancestors[loop]): if not isinstance(ancestor, ast.For): return False for directive in metadata.get(ancestor, OMPDirective): if 'collapse' in directive.s: # FIXME: check loop depth and range canonicalization if node not in self.pure_expressions: raise PythranSyntaxError( "not pure expression used as loop target inside a " "collapse clause", loop) return True assert False, "unreachable state" def gen_for(self, node, target, local_iter, local_iter_decl, loop_body): """ Create For representation on iterator for Cxx generation. Examples -------- >> "omp parallel for" >> for i in range(10): >> ... do things ... Becomes >> "omp parallel for shared(__iterX)" >> for(decltype(__iterX)::iterator __targetX = __iterX.begin(); __targetX < __iterX.end(); ++__targetX) >> auto&& i = *__targetX; >> ... do things ... It the case of not local variable, typing for `i` disappear and typing is removed for iterator in case of yields statement in function. """ # Choose target variable for iterator (which is iterator type) local_target = "__target{0}".format(id(node)) local_target_decl = self.types.builder.IteratorOfType(local_iter_decl) islocal = (node.target.id not in self.openmp_deps and node.target.id in self.scope[node] and not hasattr(self, 'yields')) # If variable is local to the for body it's a ref to the iterator value # type if islocal: local_type = "auto&&" self.ldecls.remove(node.target.id) else: local_type = "" # Assign iterable value loop_body_prelude = Statement("{} {}= *{}".format(local_type, target, local_target)) # Create the loop assign = self.make_assign(local_target_decl, local_target, local_iter) loop = For("{}.begin()".format(assign), "{0} < {1}.end()".format(local_target, local_iter), "++{0}".format(local_target), Block([loop_body_prelude, loop_body])) return [self.process_omp_attachements(node, loop)] def handle_real_loop_comparison(self, args, target, upper_bound): """ Handle comparison for real loops. Add the correct comparison operator if possible. """ # order is 1 for increasing loop, -1 for decreasing loop and 0 if it is # not known at compile time if len(args) <= 2: order = 1 elif isnum(args[2]): order = -1 + 2 * (int(args[2].value) > 0) elif isnum(args[1]) and isnum(args[0]): order = -1 + 2 * (int(args[1].value) > int(args[0].value)) else: order = 0 comparison = "{} < {}" if order == 1 else "{} > {}" comparison = comparison.format(target, upper_bound) return comparison def gen_c_for(self, node, local_iter, loop_body): """ Create C For representation for Cxx generation. Examples -------- >> for i in range(10): >> ... do things ... Becomes >> for(long i = 0, __targetX = 10; i < __targetX; i += 1) >> ... do things ... Or >> for i in range(10, 0, -1): >> ... do things ... Becomes >> for(long i = 10, __targetX = 0; i > __targetX; i += -1) >> ... do things ... It the case of not local variable, typing for `i` disappear """ args = node.iter.args step = "1L" if len(args) <= 2 else self.visit(args[2]) if len(args) == 1: lower_bound = "0L" upper_arg = 0 else: lower_bound = self.visit(args[0]) upper_arg = 1 upper_type = iter_type = "long " upper_value = self.visit(args[upper_arg]) if self.is_in_collapse(node, args[upper_arg]): upper_bound = upper_value # compatible with collapse else: upper_bound = "__target{0}".format(id(node)) islocal = (node.target.id not in self.openmp_deps and node.target.id in self.scope[node] and not hasattr(self, 'yields')) # If variable is local to the for body keep it local... if islocal: loop = list() self.ldecls.remove(node.target.id) else: # For yield function, upper_bound is globals. iter_type = "" # Back one step to keep Python behavior (except for break) loop = [If("{} == {}".format(local_iter, upper_bound), Statement("{} -= {}".format(local_iter, step)))] comparison = self.handle_real_loop_comparison(args, local_iter, upper_bound) forloop = For("{0} {1}={2}".format(iter_type, local_iter, lower_bound), comparison, "{0} += {1}".format(local_iter, step), loop_body) loop.insert(0, self.process_omp_attachements(node, forloop)) # Store upper bound value if needed if upper_bound is upper_value: header = [] else: assgnt = self.make_assign(upper_type, upper_bound, upper_value) header = [Statement(assgnt)] return header, loop def handle_omp_for(self, node, local_iter): """ Fix OpenMP directives on For loops. Add the target as private variable as a new variable may have been introduce to handle cxx iterator. Also, add the iterator as shared variable as all 'parallel for chunck' have to use the same iterator. """ for directive in metadata.get(node, OMPDirective): if any(key in directive.s for key in (' parallel ', ' task ')): # Eventually add local_iter in a shared clause as iterable is # shared in the for loop (for every clause with datasharing) directive.s += ' shared({})' directive.deps.append(ast.Name(local_iter, ast.Load(), None, None)) directive.shared_deps.append(directive.deps[-1]) target = node.target assert isinstance(target, ast.Name) hasfor = 'for' in directive.s nodefault = 'default' not in directive.s noindexref = all(isinstance(x, ast.Name) and x.id != target.id for x in directive.deps) if (hasfor and nodefault and noindexref and target.id not in self.scope[node]): # Target is private by default in omp but iterator use may # introduce an extra variable directive.s += ' private({})' directive.deps.append(ast.Name(target.id, ast.Load(), None, None)) directive.private_deps.append(directive.deps[-1]) def can_use_autofor(self, node): """ Check if given for Node can use autoFor syntax. To use auto_for: - iterator should have local scope - yield should not be use - OpenMP pragma should not be use TODO : Yield should block only if it is use in the for loop, not in the whole function. """ auto_for = (isinstance(node.target, ast.Name) and node.target.id in self.scope[node] and node.target.id not in self.openmp_deps) auto_for &= not metadata.get(node, OMPDirective) return auto_for def can_use_c_for(self, node): """ Check if a for loop can use classic C syntax. To use C syntax: - target should not be assign in the loop - range should be use as iterator - order have to be known at compile time """ assert isinstance(node.target, ast.Name) pattern_range = ast.Call(func=ast.Attribute( value=ast.Name('builtins', ast.Load(), None, None), attr='range', ctx=ast.Load()), args=AST_any(), keywords=[]) is_assigned = set() for stmt in node.body: is_assigned.update({n.id for n in self.gather(IsAssigned, stmt)}) nodes = ASTMatcher(pattern_range).search(node.iter) if node.iter not in nodes or node.target.id in is_assigned: return False args = node.iter.args if len(args) < 3: return True if isnum(args[2]): return True return False def make_assign(self, local_iter_decl, local_iter, iterable): return "{0} {1} = {2}".format(local_iter_decl, local_iter, iterable) @cxx_loop def visit_For(self, node): """ Create For representation for Cxx generation. Examples -------- >> for i in range(10): >> ... work ... Becomes >> typename returnable<decltype(builtins.range(10))>::type __iterX = builtins.range(10); >> ... possible container size reservation ... >> for (auto&& i: __iterX) >> ... the work ... This function also handle assignment for local variables. We can notice that three kind of loop are possible: - Normal for loop on iterator - Autofor loop. - Normal for loop using integer variable iteration Kind of loop used depend on OpenMP, yield use and variable scope. """ if not isinstance(node.target, ast.Name): raise PythranSyntaxError( "Using something other than an identifier as loop target", node.target) target = self.visit(node.target) # Handle the body of the for loop loop_body = Block([self.visit(stmt) for stmt in node.body]) # Declare local variables at the top of the loop body loop_body = self.process_locals(node, loop_body, node.target.id) iterable = self.visit(node.iter) if self.can_use_c_for(node): header, loop = self.gen_c_for(node, target, loop_body) else: if self.can_use_autofor(node): header = [] self.ldecls.remove(node.target.id) autofor = AutoFor(target, iterable, loop_body) loop = [self.process_omp_attachements(node, autofor)] else: # Iterator declaration local_iter = "__iter{0}".format(id(node)) local_iter_decl = self.types.builder.Assignable( self.types[node.iter]) self.handle_omp_for(node, local_iter) # Assign iterable # For C loop, it avoids issues # if the upper bound is assigned in the loop asgnt = self.make_assign(local_iter_decl, local_iter, iterable) header = [Statement(asgnt)] loop = self.gen_for(node, target, local_iter, local_iter_decl, loop_body) # For xxxComprehension, it is replaced by a for loop. In this case, # pre-allocate size of container. for comp in metadata.get(node, metadata.Comprehension): header.append(Statement("pythonic::utils::reserve({0},{1})".format( comp.target, iterable))) return Block(header + loop) @cxx_loop def visit_While(self, node): """ Create While node for Cxx generation. It is a cxx_loop to handle else clause. """ test = self.visit(node.test) body = [self.visit(n) for n in node.body] stmt = While(test, Block(body)) return self.process_omp_attachements(node, stmt) def visit_Try(self, node): body = [self.visit(n) for n in node.body] except_ = list() for n in node.handlers: except_.extend(self.visit(n)) return TryExcept(Block(body), except_) def visit_ExceptHandler(self, node): name = self.visit(node.name) if node.name else None body = [self.visit(m) for m in node.body] if isinstance(node.type, ast.Tuple): return [ExceptHandler(p.attr, Block(body), name) for p in node.type.elts] else: return [ExceptHandler( node.type and node.type.attr, Block(body), name)] def visit_If(self, node): test = self.visit(node.test) body = [self.visit(n) for n in node.body] orelse = [self.visit(n) for n in node.orelse] # compound statement required for some OpenMP Directives if isnum(node.test) and node.test.value == 1: stmt = Block(body) else: stmt = If(test, Block(body), Block(orelse) if orelse else None) return self.process_locals(node, self.process_omp_attachements(node, stmt)) def visit_Raise(self, node): exc = node.exc and self.visit(node.exc) return Statement("throw {0}".format(exc or "")) def visit_Assert(self, node): params = [self.visit(node.test), node.msg and self.visit(node.msg)] sparams = ", ".join(_f for _f in params if _f) return Statement("pythonic::pythran_assert({0})".format(sparams)) def visit_Import(self, _): return Nop() # everything is already #included def visit_ImportFrom(self, _): assert False, "should be filtered out by the expand_import pass" def visit_Expr(self, node): stmt = Statement(self.visit(node.value)) return self.process_locals(node, self.process_omp_attachements(node, stmt)) def visit_Pass(self, node): stmt = EmptyStatement() return self.process_omp_attachements(node, stmt) def visit_Break(self, _): """ Generate break statement in most case and goto for orelse clause. See Also : cxx_loop """ if self.break_handlers and self.break_handlers[-1]: self.used_break.add(self.break_handlers[-1]) return Statement("goto {0}".format(self.break_handlers[-1])) else: return Statement("break") def visit_Continue(self, _): return Statement("continue") # expr def visit_BoolOp(self, node): values = [self.visit(value) for value in node.values] op = operator_to_lambda[type(node.op)] return reduce(op, values) def visit_BinOp(self, node): left = self.visit(node.left) right = self.visit(node.right) # special case pow for positive integral exponent if ispowi(node): right = 'std::integral_constant<long, {}>{{}}'.format( node.right.value) if isstr(node.left): left = "pythonic::types::str({})".format(left) elif isstr(node.right): right = "pythonic::types::str({})".format(right) return operator_to_lambda[type(node.op)](left, right) def visit_UnaryOp(self, node): operand = self.visit(node.operand) return operator_to_lambda[type(node.op)](operand) def visit_IfExp(self, node): test = self.visit(node.test) body = self.visit(node.body) orelse = self.visit(node.orelse) return ( "(((bool){0}) " "? typename __combined<decltype({1}), decltype({2})>::type({1}) " ": typename __combined<decltype({1}), decltype({2})>::type({2}))" ).format(test, body, orelse) def visit_List(self, node): if not node.elts: # empty list return '{}(pythonic::types::empty_list())'.format(self.types[node]) else: elts = [self.visit(n) for n in node.elts] node_type = self.types[node] # constructor disambiguation, clang++ workaround if len(elts) == 1: return "{0}({1}, pythonic::types::single_value())".format( self.types.builder.Assignable(node_type).generate(self.lctx), elts[0]) else: return "{0}({{{1}}})".format( self.types.builder.Assignable(node_type).generate(self.lctx), ", ".join(elts)) def visit_Set(self, node): if not node.elts: # empty set return '{}(pythonic::types::empty_set())'.format(self.types[node]) else: elts = [self.visit(n) for n in node.elts] node_type = self.types.builder.Assignable(self.types[node]) # constructor disambiguation, clang++ workaround if len(elts) == 1: return "{0}({1}, pythonic::types::single_value())".format( self.types.builder.Assignable(node_type).generate(self.lctx), elts[0]) else: return "{0}{{{{{1}}}}}".format( node_type, ", ".join("static_cast<{}::value_type>({})" .format(node_type, elt) for elt in elts)) def visit_Dict(self, node): if not node.keys: # empty dict return '{}(pythonic::types::empty_dict())'.format(self.types[node]) else: keys = [self.visit(n) for n in node.keys] values = [self.visit(n) for n in node.values] return "{0}{{{{{1}}}}}".format( self.types.builder.Assignable(self.types[node]), ", ".join("{{ {0}, {1} }}".format(k, v) for k, v in zip(keys, values))) def visit_Tuple(self, node): elts = [self.visit(elt) for elt in node.elts] tuple_type = self.types[node] result = "pythonic::types::make_tuple({0})".format(", ".join(elts)) if isinstance(tuple_type, self.types.builder.CombinedTypes): return '({}){}'.format(tuple_type.generate(self.lctx), result) else: return result def visit_Compare(self, node): left = self.visit(node.left) ops = [operator_to_lambda[type(n)] for n in node.ops] comparators = [self.visit(n) for n in node.comparators] all_cmps = zip([left] + comparators[:-1], ops, comparators) return " and ".join(op(x, y) for x, op, y in all_cmps) def visit_Call(self, node): args = [self.visit(n) for n in node.args] func = self.visit(node.func) # special hook for getattr, as we cannot represent it in C++ if func == 'pythonic::builtins::functor::getattr{}': return ('pythonic::builtins::getattr({}{{}}, {})' .format('pythonic::types::attr::' + node.args[1].value.upper(), args[0])) else: return "{}({})".format(func, ", ".join(args)) def visit_Constant(self, node): if node.value is None: ret = 'pythonic::builtins::None' elif isinstance(node.value, bool): ret = str(node.value).lower() elif isinstance(node.value, str): quoted = node.value.replace('"', r'\"').replace('\n', r'\n') if len(node.value) == 1: quoted = quoted.replace("'", r"\'") ret = 'pythonic::types::chr(\'' + quoted + '\')' else: ret = 'pythonic::types::str("' + quoted + '")' elif isinstance(node.value, complex): ret = "{0}({1}, {2})".format( PYTYPE_TO_CTYPE_TABLE[complex], node.value.real, node.value.imag) elif isnan(node.value): ret = 'pythonic::numpy::nan' elif isinf(node.value): ret = ('+' if node.value >= 0 else '-') + 'pythonic::numpy::inf' else: ret = repr(node.value) + TYPE_TO_SUFFIX.get(type(node.value), "") if node in self.immediates: assert isinstance(node.value, int) return "std::integral_constant<%s, %s>{}" % ( PYTYPE_TO_CTYPE_TABLE[type(node.value)], str(node.value).lower()) return ret def visit_Attribute(self, node): obj, path = attr_to_path(node) sattr = '::'.join(map(cxxid, path)) if not obj.isliteral(): sattr += '{}' return sattr def all_positive(self, node): if isinstance(node, ast.Tuple): return all(self.range_values[elt].low >= 0 for elt in node.elts) return self.range_values[node].low >= 0 def visit_Subscript(self, node): value = self.visit(node.value) # we cannot overload the [] operator in that case if isstr(node.value): value = 'pythonic::types::str({})'.format(value) # positive static index case if (isnum(node.slice) and (node.slice.value >= 0) and isinstance(node.slice.value, int)): return "std::get<{0}>({1})".format(node.slice.value, value) # positive indexing case elif self.all_positive(node.slice): slice_ = self.visit(node.slice) return "{1}.fast({0})".format(slice_, value) # extended slice case elif isextslice(node.slice): slices = [self.visit(elt) for elt in node.slice.elts] return "{1}({0})".format(','.join(slices), value) # standard case else: slice_ = self.visit(node.slice) return "{1}[{0}]".format(slice_, value) def visit_Name(self, node): if node.id in self.local_names: return cxxid(node.id) elif node.id in self.global_declarations: return "{0}()".format(cxxid(node.id)) else: return cxxid(node.id) # other def visit_Slice(self, node): args = [] for field in ('lower', 'upper', 'step'): nfield = getattr(node, field) arg = (self.visit(nfield) if nfield else 'pythonic::builtins::None') args.append(arg) if node.step is None or (isnum(node.step) and node.step.value == 1): if self.all_positive(node.lower) and self.all_positive(node.upper): builder = "pythonic::types::fast_contiguous_slice({},{})" else: builder = "pythonic::types::contiguous_slice({},{})" return builder.format(args[0], args[1]) else: return "pythonic::types::slice({},{},{})".format(*args) class CxxGenerator(CxxFunction): # recover previous generator state StateHolder = "__generator_state" StateValue = "__generator_value" # flags the last statement of a generator FinalStatement = "that_is_all_folks" # local declaration processing def process_locals(self, node, node_visited, *skipped): return node_visited # no processing def prepare_functiondef_context(self, node): self.extra_declarations = [] # 0 is used as initial_state, thus the +1 self.yields = {k: (1 + v, "yield_point{0}".format(1 + v)) for (v, k) in enumerate(self.gather(YieldPoints, node))} return super(CxxGenerator, self).prepare_functiondef_context(node) # stmt def visit_FunctionDef(self, node): self.returns = False tmp = self.prepare_functiondef_context(node) operator_body, formal_types, formal_args = tmp tmp = self.prepare_types(node) dflt_argv, dflt_argt, result_type, callable_type, pure_type = tmp # a generator has a call operator that returns the iterator next_name = "__generator__{0}".format(cxxid(node.name)) instanciated_next_name = "{0}{1}".format( next_name, "<{0}>".format(", ".join(formal_types)) if formal_types else "") if self.returns: operator_body.append(Label(CxxGenerator.FinalStatement)) operator_body.append(Statement("return result_type()")) next_declaration = [ FunctionDeclaration(Value("result_type", "next"), []), EmptyStatement()] # empty statement to force a comma ... # the constructors next_constructors = [ FunctionBody( FunctionDeclaration(Value("", next_name), []), Line(': pythonic::yielder() {}') )] if formal_types: # If all parameters have a default value, we don't need default # constructor if dflt_argv and all(dflt_argv): next_constructors = list() next_constructors.append(FunctionBody( make_function_declaration(self, node, "", next_name, formal_types, formal_args, dflt_argv), Line(": {0} {{ }}".format( ", ".join(["pythonic::yielder()"] + ["{0}({0})".format(arg) for arg in formal_args]))) )) next_iterator = [ FunctionBody( FunctionDeclaration(Value("void", "operator++"), []), Block([Statement("next()")])), FunctionBody( FunctionDeclaration( Value("typename {0}::result_type".format( instanciated_next_name), "operator*"), [], "const"), Block([ ReturnStatement( CxxGenerator.StateValue)])), FunctionBody( FunctionDeclaration( Value("pythonic::types::generator_iterator<{0}>" .format(next_name), "begin"), []), Block([Statement("next()"), ReturnStatement( "pythonic::types::generator_iterator<{0}>" "(*this)".format(next_name))])), FunctionBody( FunctionDeclaration( Value("pythonic::types::generator_iterator<{0}>" .format(next_name), "end"), []), Block([ReturnStatement( "pythonic::types::generator_iterator<{0}>()" .format(next_name))])) ] next_signature = templatize( FunctionDeclaration( Value( "typename {0}::result_type".format( instanciated_next_name), "{0}::next".format(instanciated_next_name)), []), formal_types) next_body = operator_body # the dispatch table at the entry point next_body.insert(0, Statement("switch({0}) {{ {1} }}".format( CxxGenerator.StateHolder, " ".join("case {0}: goto {1};".format(num, where) for (num, where) in sorted( self.yields.values(), key=lambda x: x[0]))))) ctx = CachedTypeVisitor(self.lctx) next_members = ([Statement("{0} {1}".format(ft, fa)) for (ft, fa) in zip(formal_types, formal_args)] + [Statement("{0} {1}".format( self.types[self.local_names[k]].generate(ctx), k)) for k in self.ldecls] + [Statement("{0} {1}".format(v, k)) for k, v in self.extra_declarations] + [Statement( "typename {0}::result_type {1}".format( instanciated_next_name, CxxGenerator.StateValue))]) extern_typedefs = [Typedef(Value(t.generate(ctx), t.name)) for t in self.types[node][1]] iterator_typedef = [ Typedef( Value("pythonic::types::generator_iterator<{0}>".format( "{0}<{1}>".format(next_name, ", ".join(formal_types)) if formal_types else next_name), "iterator")), Typedef(Value(result_type.generate(ctx), "value_type"))] result_typedef = [ Typedef(Value(result_type.generate(ctx), "result_type"))] extra_typedefs = (ctx.typedefs() + extern_typedefs + iterator_typedef + result_typedef) next_struct = templatize( Struct(next_name, extra_typedefs + next_members + next_constructors + next_iterator + next_declaration, "pythonic::yielder"), formal_types) next_definition = FunctionBody(next_signature, Block(next_body)) operator_declaration = [ templatize( make_const_function_declaration( self, node, instanciated_next_name, "operator()", formal_types, formal_args, dflt_argv), formal_types, dflt_argt), EmptyStatement()] operator_signature = make_const_function_declaration( self, node, instanciated_next_name, "{0}::operator()".format(cxxid(node.name)), formal_types, formal_args) operator_definition = FunctionBody( templatize(operator_signature, formal_types), Block([ReturnStatement("{0}({1})".format( instanciated_next_name, ", ".join(formal_args)))]) ) topstruct_type = templatize( Struct("type", extra_typedefs), formal_types) topstruct = Struct( cxxid(node.name), [topstruct_type, callable_type, pure_type] + operator_declaration) return [next_struct, topstruct], [next_definition, operator_definition] def visit_Return(self, node): self.returns = True return Block([Statement("{0} = -1".format(CxxGenerator.StateHolder)), Statement("goto {0}".format(CxxGenerator.FinalStatement)) ]) def visit_Yield(self, node): num, label = self.yields[node] return "".join(n for n in Block([ Assign(CxxGenerator.StateHolder, num), ReturnStatement("{0} = {1}".format(CxxGenerator.StateValue, self.visit(node.value))), Statement("{0}:".format(label)) ]).generate()) def visit_Assign(self, node): value = self.visit(node.value) targets = [self.visit(t) for t in node.targets] alltargets = "= ".join(targets) stmt = Assign(alltargets, value) return self.process_omp_attachements(node, stmt) def can_use_autofor(self, node): """ TODO : Yield should block only if it is use in the for loop, not in the whole function. """ return False def make_assign(self, local_iter_decl, local_iter, iterable): # For yield function, iterable is globals. self.extra_declarations.append((local_iter, local_iter_decl,)) return super(CxxGenerator, self).make_assign("", local_iter, iterable) class Cxx(Backend): """ Produces a C++ representation of the AST. >>> import gast as ast, pythran.passmanager as passmanager, os >>> node = ast.parse("def foo(): return 'hello world'") >>> pm = passmanager.PassManager('test') >>> r = pm.dump(Cxx, node) >>> print(str(r).replace(os.sep, '/')) #include <pythonic/include/types/str.hpp> #include <pythonic/types/str.hpp> namespace __pythran_test { struct foo { typedef void callable; typedef void pure; struct type { typedef typename pythonic::returnable<pythonic::types::str>::type \ result_type; } ; inline typename type::result_type operator()() const; ; } ; inline typename foo::type::result_type foo::operator()() const { return pythonic::types::str("hello world"); } } """ def __init__(self): """ Basic initialiser gathering analysis informations. """ self.result = None super(Cxx, self).__init__(Dependencies, GlobalDeclarations, Types, Scope, RangeValues, PureExpressions, Immediates, Ancestors) # mod def visit_Module(self, node): """ Build a compilation unit. """ # build all types header_deps = sorted(self.dependencies) headers = [Include(os.path.join("pythonic", "include", *map(cxxid, t)) + ".hpp") for t in header_deps] headers += [Include(os.path.join("pythonic", *map(cxxid, t)) + ".hpp") for t in header_deps] decls_n_defns = list(filter(None, (self.visit(stmt) for stmt in node.body))) decls, defns = zip(*decls_n_defns) if decls_n_defns else ([], []) nsbody = [s for ls in decls + defns for s in ls] ns = Namespace(pythran_ward + self.passmanager.module_name, nsbody) self.result = CompilationUnit(headers + [ns]) def visit_FunctionDef(self, node): yields = self.gather(YieldPoints, node) visitor = (CxxGenerator if yields else CxxFunction)(self) return visitor.visit(node)