%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)