#!/usr/bin/python
# -*- coding: utf-8 -*-
import importlib
from numpy import ndarray
from sympy import Lambda, preorder_traversal
from sympy.core.expr import Expr, AtomicExpr
from sympy.core import Symbol, Tuple
from sympy.core.relational import Equality, Relational, Ne, Eq
from sympy.logic.boolalg import And, Boolean, Not, Or, true, false
from sympy.core.singleton import Singleton
from sympy.core.function import Function, Application
from sympy import sympify
from sympy import Symbol, Integer, Add, Mul, Pow as sp_Pow
from sympy import Integer as sp_Integer
from sympy import Float as sp_Float ,Rational as sp_Rational
from sympy.core.compatibility import with_metaclass
from sympy.core.compatibility import is_sequence
from sympy.core.assumptions import StdFactKB
from sympy import cache
# from sympy.sets.fancysets import Range as sm_Range
from sympy.tensor import Idx, Indexed, IndexedBase
from sympy.matrices import ImmutableDenseMatrix
from sympy.matrices.expressions.matexpr import MatrixSymbol, \
MatrixElement
from sympy.utilities.iterables import iterable
from sympy.logic.boolalg import Boolean, BooleanTrue, BooleanFalse
from sympy.core.basic import Atom
from sympy.core.expr import Expr, AtomicExpr
from sympy.core.compatibility import string_types
from sympy.core.operations import LatticeOp
from sympy.core.function import Derivative, UndefinedFunction
from sympy.core.function import _coeff_isneg
from sympy.core.singleton import S
from sympy import Integral, Symbol
from sympy.simplify.radsimp import fraction
from sympy.logic.boolalg import BooleanFunction
import collections
from sympy.core.compatibility import is_sequence
# TODO - add EmptyStmt => empty lines
# - update code examples
# - add examples
# - Function case
# - Zeros, Ones, Array cases
# - AnnotatedComment case
# - Slice case
# - use Tuple after checking the object is iterable:'funcs=Tuple(*funcs)'
# - add a new Idx that uses Variable instead of Symbol
from .basic import Basic
from .datatypes import datatype, DataType, CustomDataType, NativeSymbol, \
NativeInteger, NativeBool, NativeReal, \
NativeComplex, NativeRange, NativeTensor, NativeString, \
NativeGeneric
local_sympify = {'N': Symbol('N'), 'S': Symbol('S'),
'zeros':Symbol('zeros'),'ones':Symbol('ones')
,'Point':Symbol('Point')}
[docs]def subs(expr, new_elements):
"""
Substitutes old for new in an expression after sympifying args.
new_elements : list of tuples like [(x,2)(y,3)]
"""
if len(list(new_elements)) == 0:
return expr
if isinstance(expr, (list, tuple, Tuple)):
return [subs(i, new_elements) for i in expr]
elif isinstance(expr, While):
test = subs(expr.test, new_elements)
body = subs(expr.body, new_elements)
return While(test, body)
elif isinstance(expr, For):
target = subs(expr.target, new_elements)
it = subs(expr.iterable, new_elements)
target = expr.target
it = expr.iterable
body = subs(expr.body, new_elements)
return For(target, it, body)
elif isinstance(expr, If):
args = []
for block in expr.args:
test = block[0]
stmts = block[1]
t = subs(test, new_elements)
s = subs(stmts, new_elements)
args.append((t, s))
return If(*args)
elif isinstance(expr, Return):
for i in new_elements:
expr = expr.subs(i[0],i[1])
return expr
elif isinstance(expr, Assign):
new_expr = expr.subs(new_elements)
new_expr.set_fst(expr.fst)
return new_expr
elif isinstance(expr, Expr):
return expr.subs(new_elements)
else:
return expr
[docs]def allocatable_like(expr, verbose=False):
"""
finds attributs of an expression
expr: Expr
a pyccel expression
verbose: bool
talk more
"""
if isinstance(expr, (Variable, IndexedVariable, IndexedElement)):
return expr
elif isinstance(expr, str):
# if the rhs is a string
return expr
elif isinstance(expr, Expr):
args = [expr]
while args:
a = args.pop()
# XXX: This is a hack to support non-Basic args
if isinstance(a, string_types):
continue
if a.is_Mul:
if _coeff_isneg(a):
if a.args[0] is S.NegativeOne:
a = a.as_two_terms()[1]
else:
a = -a
(n, d) = fraction(a)
if n.is_Integer:
args.append(d)
continue # won't be -Mul but could be Add
elif d is not S.One:
if not d.is_Integer:
args.append(d)
args.append(n)
continue # could be -Mul
elif a.is_Add:
aargs = list(a.args)
negs = 0
for (i, ai) in enumerate(aargs):
if _coeff_isneg(ai):
negs += 1
args.append(-ai)
else:
args.append(ai)
continue
if a.is_Pow and a.exp is S.NegativeOne:
args.append(a.base) # won't be -Mul but could be Add
continue
if a.is_Mul or a.is_Pow or a.is_Function or isinstance(a,
Derivative) or isinstance(a, Integral):
o = Symbol(a.func.__name__.upper())
if not a.is_Symbol and not isinstance(a, (IndexedElement,
Function)):
args.extend(a.args)
if isinstance(a, Function):
if verbose:
print('Functions not yet available')
return None
elif isinstance(a, (Variable, IndexedVariable,
IndexedElement)):
return a
elif a.is_Symbol:
raise TypeError('Found an unknown symbol {0}'.format(str(a)))
else:
raise TypeError('Unexpected type {0}'.format(type(expr)))
def _atomic(e, cls=None,ignore=()):
"""Return atom-like quantities as far as substitution is
concerned: Functions and DottedVarviables, Variables. we don't
return atoms that are inside such quantities too
"""
from sympy import preorder_traversal
from collections import OrderedDict
pot = preorder_traversal(e)
seen = []
atoms_ = []
if cls is None:
cls = (Application, DottedVariable, Variable,
IndexedVariable,IndexedElement)
for p in pot:
if p in seen or isinstance(p,ignore):
pot.skip()
continue
seen.append(p)
if isinstance(p, cls):
pot.skip()
atoms_.append(p)
return atoms_
[docs]def atom(e):
"""Return atom-like quantities as far as substitution is
concerned: Functions , DottedVarviables. contrary to _atom we
return atoms that are inside such quantities too
"""
pass
[docs]def inline(func, args):
local_vars = func.local_vars
body = func.body
body = subs(body, zip(func.arguments, args))
return Block(str(func.name), local_vars, body)
[docs]def float2int(expr):
atoms = expr.atoms(sp_Float)
m = map(sp_Integer,atoms)
return expr.subs(zip(atoms,m))
[docs]def int2float(expr):
if expr.is_number:
return expr.n()
atoms = _atomic(expr, cls=sp_Rational, ignore=Function)
if atoms:
m = map(sp_Float,atoms)
expr = expr.subs(zip(atoms,m))
return expr
[docs]class Pow(sp_Pow):
def _eval_subs(self, old, new):
args = self.args
args_ = [self.base._subs(old, new),self.exp._subs(old, new)]
args = [args_[i] if args_[i] else args[i] for i in range(len(args))]
expr = Pow(args[0], args[1], evaluate=False)
return expr
def _eval_evalf(self,prec):
return sp_Pow(self.base,self.exp).evalf(prec)
[docs]class DottedName(Basic):
"""
Represents a dotted variable.
Examples
>>> from pyccel.ast.core import DottedName
>>> DottedName('matrix', 'n_rows')
matrix.n_rows
>>> DottedName('pyccel', 'stdlib', 'parallel')
pyccel.stdlib.parallel
"""
def __new__(cls, *args):
return Basic.__new__(cls, *args)
@property
def name(self):
return self._args
def __str__(self):
return """.""".join(str(n) for n in self.name)
def _sympystr(self, printer):
sstr = printer.doprint
return """.""".join(sstr(n) for n in self.name)
[docs]class AsName(Basic):
"""
Represents a renaming of a variable, used with Import.
Examples
>>> from pyccel.ast.core import AsName
>>> AsName('new', 'old')
new as old
"""
def __new__(cls, name, target):
# TODO check
return Basic.__new__(cls, name, target)
@property
def name(self):
return self._args[0]
@property
def target(self):
return self._args[1]
def _sympystr(self, printer):
sstr = printer.doprint
return '{0} as {1}'.format(sstr(self.name), sstr(self.target))
[docs]class List(Tuple):
"""Represent lists in the code with dynamic memory management."""
pass
[docs]class Dlist(Basic):
""" this is equivalent to the zeros function of numpy arrays for the python list.
value : Expr
a sympy expression which represents the initilized value of the list
shape : the shape of the array
"""
def __new__(cls, val, length):
return Basic.__new__(cls, val, length)
@property
def val(self):
return self._args[0]
@property
def length(self):
return self._args[1]
[docs]class Assign(Basic):
"""Represents variable assignment for code generation.
lhs : Expr
Sympy object representing the lhs of the expression. These should be
singular objects, such as one would use in writing code. Notable types
include Symbol, MatrixSymbol, MatrixElement, and Indexed. Types that
subclass these types are also supported.
rhs : Expr
Sympy object representing the rhs of the expression. This can be any
type, provided its shape corresponds to that of the lhs. For example,
a Matrix type can be assigned to MatrixSymbol, but not to Symbol, as
the dimensions will not align.
strict: bool
if True, we do some verifications. In general, this can be more
complicated and is treated in pyccel.syntax.
status: None, str
if lhs is not allocatable, then status is None.
otherwise, status is {'allocated', 'unallocated'}
like: None, Variable
contains the name of the variable from which the lhs will be cloned.
Examples
>>> from sympy import symbols, MatrixSymbol, Matrix
>>> from pyccel.ast.core import Assign
>>> x, y, z = symbols('x, y, z')
>>> Assign(x, y)
x := y
>>> Assign(x, 0)
x := 0
>>> A = MatrixSymbol('A', 1, 3)
>>> mat = Matrix([x, y, z]).T
>>> Assign(A, mat)
A := Matrix([[x, y, z]])
>>> Assign(A[0, 1], x)
A[0, 1] := x
"""
def __new__(
cls,
lhs,
rhs,
strict=False,
status=None,
like=None,
):
cls._strict = strict
if strict:
lhs = sympify(lhs, locals=local_sympify)
rhs = sympify(rhs, locals=local_sympify)
# Tuple of things that can be on the lhs of an assignment
assignable = (Symbol, MatrixSymbol, MatrixElement, Indexed,
Idx)
# if not isinstance(lhs, assignable):
# raise TypeError("Cannot assign to lhs of type %s." % type(lhs))
# Indexed types implement shape, but don't define it until later. This
# causes issues in assignment validation. For now, matrices are defined
# as anything with a shape that is not an Indexed
lhs_is_mat = hasattr(lhs, 'shape') and not isinstance(lhs,
Indexed)
rhs_is_mat = hasattr(rhs, 'shape') and not isinstance(rhs,
Indexed)
# If lhs and rhs have same structure, then this assignment is ok
if lhs_is_mat:
if not rhs_is_mat:
raise ValueError('Cannot assign a scalar to a matrix.')
elif lhs.shape != rhs.shape:
raise ValueError("Dimensions of lhs and rhs don't align.")
elif rhs_is_mat and not lhs_is_mat:
raise ValueError('Cannot assign a matrix to a scalar.')
return Basic.__new__(cls, lhs, rhs, status, like)
def _sympystr(self, printer):
sstr = printer.doprint
return '{0} := {1}'.format(sstr(self.lhs), sstr(self.rhs))
@property
def lhs(self):
return self._args[0]
@property
def rhs(self):
return self._args[1]
# TODO : remove
@property
def expr(self):
return self.rhs
@property
def status(self):
return self._args[2]
@property
def like(self):
return self._args[3]
@property
def strict(self):
return self._strict
@property
def is_alias(self):
"""Returns True if the assignment is an alias."""
# TODO to be improved when handling classes
lhs = self.lhs
rhs = self.rhs
cond = isinstance(rhs, Variable) and rhs.rank > 0
cond = cond or isinstance(rhs, IndexedElement)
cond = cond or isinstance(rhs, IndexedVariable)
cond = cond and isinstance(lhs, Symbol)
cond = cond or isinstance(rhs, Variable) and rhs.is_pointer
return cond
@property
def is_symbolic_alias(self):
"""Returns True if the assignment is a symbolic alias."""
# TODO to be improved when handling classes
lhs = self.lhs
rhs = self.rhs
if isinstance(lhs, Variable):
return isinstance(lhs.dtype, NativeSymbol)
elif isinstance(lhs, Symbol):
if isinstance(rhs, Range):
return True
elif isinstance(rhs, Variable):
return isinstance(rhs.dtype, NativeSymbol)
elif isinstance(rhs, Symbol):
return True
return False
[docs]class CodeBlock(Basic):
"""Represents a list of stmt for code generation.
we use it when a single statement in python
produce multiple statement in the targeted language
"""
def __new__(cls, body):
ls = []
for i in body:
if isinstance(i, CodeBlock):
ls += i.body
elif isinstance(i, (Assign, For, AugAssign, FunctionalFor,
Application)):
ls.append(i)
else:
raise TypeError('statement not supported yet')
obj = Basic.__new__(cls, ls)
if isinstance(ls[-1], (Assign, AugAssign)):
obj.set_fst(ls[-1].fst)
return obj
@property
def body(self):
return self._args[0]
@property
def lhs(self):
return self.body[-1].lhs
[docs]class AliasAssign(Basic):
"""Represents aliasing for code generation. An alias is any statement of the
form `lhs := rhs` where
lhs : Symbol
at this point we don't know yet all information about lhs, this is why a
Symbol is the appropriate type.
rhs : Variable, IndexedVariable, IndexedElement
an assignable variable can be of any rank and any datatype, however its
shape must be known (not None)
Examples
>>> from sympy import Symbol
>>> from pyccel.ast.core import AliasAssign
>>> from pyccel.ast.core import Variable
>>> n = Variable('int', 'n')
>>> x = Variable('int', 'x', rank=1, shape=[n])
>>> y = Symbol('y')
>>> AliasAssign(y, x)
"""
def __new__(cls, lhs, rhs):
return Basic.__new__(cls, lhs, rhs)
def _sympystr(self, printer):
sstr = printer.doprint
return '{0} := {1}'.format(sstr(self.lhs), sstr(self.rhs))
@property
def lhs(self):
return self._args[0]
@property
def rhs(self):
return self._args[1]
[docs]class SymbolicAssign(Basic):
"""Represents symbolic aliasing for code generation. An alias is any statement of the
form `lhs := rhs` where
lhs : Symbol
rhs : Range
Examples
>>> from sympy import Symbol
>>> from pyccel.ast.core import SymbolicAssign
>>> from pyccel.ast.core import Range
>>> r = Range(0, 3)
>>> y = Symbol('y')
>>> SymbolicAssign(y, r)
"""
def __new__(cls, lhs, rhs):
return Basic.__new__(cls, lhs, rhs)
def _sympystr(self, printer):
sstr = printer.doprint
return '{0} := {1}'.format(sstr(self.lhs), sstr(self.rhs))
@property
def lhs(self):
return self._args[0]
@property
def rhs(self):
return self._args[1]
# The following are defined to be sympy approved nodes. If there is something
# smaller that could be used, that would be preferable. We only use them as
# tokens.
[docs]class NativeOp(with_metaclass(Singleton, Basic)):
"""Base type for native operands."""
pass
[docs]class AddOp(NativeOp):
_symbol = '+'
[docs]class SubOp(NativeOp):
_symbol = '-'
[docs]class MulOp(NativeOp):
_symbol = '*'
[docs]class DivOp(NativeOp):
_symbol = '/'
[docs]class ModOp(NativeOp):
_symbol = '%'
op_registry = {
'+': AddOp(),
'-': SubOp(),
'*': MulOp(),
'/': DivOp(),
'%': ModOp(),
}
[docs]def operator(op):
"""Returns the operator singleton for the given operator"""
if op.lower() not in op_registry:
raise ValueError('Unrecognized operator ' + op)
return op_registry[op]
[docs]class AugAssign(Basic):
"""
Represents augmented variable assignment for code generation.
lhs : Expr
Sympy object representing the lhs of the expression. These should be
singular objects, such as one would use in writing code. Notable types
include Symbol, MatrixSymbol, MatrixElement, and Indexed. Types that
subclass these types are also supported.
op : NativeOp
Operator (+, -, /, \*, %).
rhs : Expr
Sympy object representing the rhs of the expression. This can be any
type, provided its shape corresponds to that of the lhs. For example,
a Matrix type can be assigned to MatrixSymbol, but not to Symbol, as
the dimensions will not align.
strict: bool
if True, we do some verifications. In general, this can be more
complicated and is treated in pyccel.syntax.
status: None, str
if lhs is not allocatable, then status is None.
otherwise, status is {'allocated', 'unallocated'}
like: None, Variable
contains the name of the variable from which the lhs will be cloned.
Examples
>>> from pyccel.ast.core import Variable
>>> from pyccel.ast.core import AugAssign
>>> s = Variable('int', 's')
>>> t = Variable('int', 't')
>>> AugAssign(s, '+', 2 * t + 1)
s += 1 + 2*t
"""
def __new__(
cls,
lhs,
op,
rhs,
strict=False,
status=None,
like=None,
):
cls._strict = strict
if strict:
lhs = sympify(lhs, locals=local_sympify)
rhs = sympify(rhs, locals=local_sympify)
# Tuple of things that can be on the lhs of an assignment
assignable = (Symbol, MatrixSymbol, MatrixElement, Indexed)
if not isinstance(lhs, assignable):
raise TypeError('Cannot assign to lhs of type %s.'
% type(lhs))
# Indexed types implement shape, but don't define it until later. This
# causes issues in assignment validation. For now, matrices are defined
# as anything with a shape that is not an Indexed
lhs_is_mat = hasattr(lhs, 'shape') and not isinstance(lhs,
Indexed)
rhs_is_mat = hasattr(rhs, 'shape') and not isinstance(rhs,
Indexed)
# If lhs and rhs have same structure, then this assignment is ok
if lhs_is_mat:
if not rhs_is_mat:
raise ValueError('Cannot assign a scalar to a matrix.'
)
elif lhs.shape != rhs.shape:
raise ValueError("Dimensions of lhs and rhs don't align."
)
elif rhs_is_mat and not lhs_is_mat:
raise ValueError('Cannot assign a matrix to a scalar.')
if isinstance(op, str):
op = operator(op)
elif op not in list(op_registry.values()):
raise TypeError('Unrecognized Operator')
return Basic.__new__(
cls,
lhs,
op,
rhs,
status,
like,
)
def _sympystr(self, printer):
sstr = printer.doprint
return '{0} {1}= {2}'.format(sstr(self.lhs), self.op._symbol,
sstr(self.rhs))
@property
def lhs(self):
return self._args[0]
@property
def op(self):
return self._args[1]
@property
def rhs(self):
return self._args[2]
@property
def status(self):
return self._args[3]
@property
def like(self):
return self._args[4]
@property
def strict(self):
return self._strict
[docs]class While(Basic):
"""Represents a 'while' statement in the code.
Expressions are of the form:
"while test:
body..."
test : expression
test condition given as a sympy expression
body : sympy expr
list of statements representing the body of the While statement.
Examples
>>> from sympy import Symbol
>>> from pyccel.ast.core import Assign, While
>>> n = Symbol('n')
>>> While((n>1), [Assign(n,n-1)])
While(n > 1, (n := n - 1,))
"""
def __new__(cls, test, body):
test = sympify(test, locals=local_sympify)
if not iterable(body):
raise TypeError('body must be an iterable')
body = Tuple(*(sympify(i, locals=local_sympify) for i in body))
return Basic.__new__(cls, test, body)
@property
def test(self):
return self._args[0]
@property
def body(self):
return self._args[1]
[docs]class With(Basic):
"""Represents a 'with' statement in the code.
Expressions are of the form:
"while test:
body..."
test : expression
test condition given as a sympy expression
body : sympy expr
list of statements representing the body of the With statement.
Examples
"""
# TODO check prelude and epilog
def __new__(
cls,
test,
body,
settings,
):
test = sympify(test, locals=local_sympify)
if not iterable(body):
raise TypeError('body must be an iterable')
body = Tuple(*(sympify(i, locals=local_sympify) for i in body))
return Basic.__new__(cls, test, body, settings)
@property
def test(self):
return self._args[0]
@property
def body(self):
return self._args[1]
@property
def settings(self):
return self._args[2]
@property
def block(self):
methods = self.test.cls_base.methods
for i in methods:
if str(i.name) == '__enter__':
enter = i
elif str(i.name) == '__exit__':
exit = i
enter = inline(enter,[])
exit = inline(exit, [])
# TODO check if enter is empty or not first
body = enter.body
body += self.body
body += exit.body
return Block('with', [], body)
[docs]class Product(Basic):
"""
Represents a Product stmt.
"""
def __new__(cls, *args):
if not isinstance(args, (tuple, list, Tuple)):
raise TypeError('args must be an iterable')
elif len(args) < 2:
raise ValueError('args must be of lenght > 2')
return Basic.__new__(cls, *args)
@property
def elements(self):
return self._args
[docs]class Zip(Basic):
"""
Represents a zip stmt.
"""
def __new__(cls, *args):
if not isinstance(args, (tuple, list, Tuple)):
raise TypeError('args must be an iterable')
elif len(args) < 2:
raise ValueError('args must be of lenght > 2')
return Basic.__new__(cls, *args)
@property
def element(self):
return self._args[0]
[docs]class Enumerate(Basic):
"""
Reresents the enumerate stmt
"""
def __new__(cls, arg):
if not isinstance(arg, (Symbol, Indexed, IndexedBase)):
raise TypeError('Expecting an arg of valid type')
return Basic.__new__(cls, arg)
@property
def element(self):
return self._args[0]
[docs]class Map(Basic):
"""
Reresents the map stmt
"""
def __new__(cls, *args):
if len(args)<2:
raise TypeError('wrong number of arguments')
return Basic.__new__(cls, *args)
[docs]class Range(Basic):
"""
Represents a range.
Examples
>>> from pyccel.ast.core import Variable
>>> from pyccel.ast.core import Range
>>> from sympy import Symbol
>>> s = Variable('int', 's')
>>> e = Symbol('e')
>>> Range(s, e, 1)
Range(0, n, 1)
"""
def __new__(cls, *args):
start = 0
stop = None
step = 1
_valid_args = (Integer, Symbol, Indexed, Variable,
IndexedElement)
if isinstance(args, (tuple, list, Tuple)):
if len(args) == 1:
stop = args[0]
elif len(args) == 2:
start = args[0]
stop = args[1]
elif len(args) == 3:
start = args[0]
stop = args[1]
step = args[2]
else:
raise ValueError('Range has at most 3 arguments')
elif isinstance(args, _valid_args):
stop = args
else:
raise TypeError('expecting a list or valid stop')
return Basic.__new__(cls, start, stop, step)
@property
def start(self):
return self._args[0]
@property
def stop(self):
return self._args[1]
@property
def step(self):
return self._args[2]
@property
def size(self):
return (self.stop - self.start) / self.step
[docs]class Tile(Range):
"""
Representes a tile.
Examples
>>> from pyccel.ast.core import Variable
>>> from pyccel.ast.core import Tile
>>> from sympy import Symbol
>>> s = Variable('int', 's')
>>> e = Symbol('e')
>>> Tile(s, e, 1)
Tile(0, n, 1)
"""
def __new__(cls, start, stop):
step = 1
return Range.__new__(cls, start, stop, step)
@property
def start(self):
return self._args[0]
@property
def stop(self):
return self._args[1]
@property
def size(self):
return self.stop - self.start
[docs]class ParallelRange(Range):
"""
Representes a parallel range using OpenMP/OpenACC.
Examples
>>> from pyccel.ast.core import Variable
"""
pass
# TODO: implement it as an extension of sympy Tensor?
[docs]class Tensor(Basic):
"""
Base class for tensor.
Examples
>>> from pyccel.ast.core import Variable
>>> from pyccel.ast.core import Range, Tensor
>>> from sympy import Symbol
>>> s1 = Variable('int', 's1')
>>> s2 = Variable('int', 's2')
>>> e1 = Variable('int', 'e1')
>>> e2 = Variable('int', 'e2')
>>> r1 = Range(s1, e1, 1)
>>> r2 = Range(s2, e2, 1)
>>> Tensor(r1, r2)
Tensor(Range(s1, e1, 1), Range(s2, e2, 1), name=tensor)
"""
def __new__(cls, *args, **kwargs):
for r in args:
cond = isinstance(r, Variable) and isinstance(r.dtype,
(NativeRange, NativeTensor))
cond = cond or isinstance(r, (Range, Tensor))
if not cond:
raise TypeError('non valid argument, given {0}'.format(type(r)))
try:
name = kwargs['name']
except:
name = 'tensor'
args = list(args) + [name]
return Basic.__new__(cls, *args)
@property
def name(self):
return self._args[-1]
@property
def ranges(self):
return self._args[:-1]
@property
def dim(self):
return len(self.ranges)
def _sympystr(self, printer):
sstr = printer.doprint
txt = ', '.join(sstr(n) for n in self.ranges)
txt = 'Tensor({0}, name={1})'.format(txt, sstr(self.name))
return txt
# TODO add a name to a block?
[docs]class Block(Basic):
"""Represents a block in the code. A block consists of the following inputs
variables: list
list of the variables that appear in the block.
declarations: list
list of declarations of the variables that appear in the block.
body: list
a list of statements
Examples
>>> from pyccel.ast.core import Variable, Assign, Block
>>> n = Variable('int', 'n')
>>> x = Variable('int', 'x')
>>> Block([n, x], [Assign(x,2.*n + 1.), Assign(n, n + 1)])
Block([n, x], [x := 1.0 + 2.0*n, n := 1 + n])
"""
def __new__(
cls,
name,
variables,
body):
if not isinstance(name, str):
raise TypeError('name must be of type str')
if not iterable(variables):
raise TypeError('variables must be an iterable')
for var in variables:
if not isinstance(var, Variable):
raise TypeError('Only a Variable instance is allowed.')
if not iterable(body):
raise TypeError('body must be an iterable')
body = Tuple(*body)
return Basic.__new__(cls, name, variables, body)
@property
def name(self):
return self._args[0]
@property
def variables(self):
return self._args[1]
@property
def body(self):
return self._args[2]
@property
def declarations(self):
return [Declare(i.dtype, i) for i in self.variables]
[docs]class ParallelBlock(Block):
"""
Represents a parallel block in the code.
In addition to block inputs, there is
clauses: list
a list of clauses
Examples
>>> from pyccel.ast.core import ParallelBlock
>>> from pyccel.ast.core import Variable, Assign, Block
>>> n = Variable('int', 'n')
>>> x = Variable('int', 'x')
>>> body = [Assign(x,2.*n + 1.), Assign(n, n + 1)]
>>> variables = [x,n]
>>> clauses = []
>>> ParallelBlock(clauses, variables, body)
# parallel
x := 1.0 + 2.0*n
n := 1 + n
"""
_prefix = '#'
def __new__(
cls,
clauses,
variables,
body,
):
if not iterable(clauses):
raise TypeError('Expecting an iterable for clauses')
cls._clauses = clauses
return Block.__new__(cls, variables, body)
@property
def clauses(self):
return self._clauses
@property
def prefix(self):
return self._prefix
def _sympystr(self, printer):
sstr = printer.doprint
prefix = sstr(self.prefix)
clauses = ' '.join('{0}'.format(sstr(i)) for i in self.clauses)
body = '\n'.join('{0}'.format(sstr(i)) for i in self.body)
code = '{0} parallel {1}\n{2}'.format(prefix, clauses, body)
return code
[docs]class Module(Basic):
"""Represents a module in the code. A block consists of the following inputs
variables: list
list of the variables that appear in the block.
declarations: list
list of declarations of the variables that appear in the block.
funcs: list
a list of FunctionDef instances
classes: list
a list of ClassDef instances
imports: list, tuple
list of needed imports
Examples
>>> from pyccel.ast.core import Variable, Assign
>>> from pyccel.ast.core import ClassDef, FunctionDef, Module
>>> x = Variable('real', 'x')
>>> y = Variable('real', 'y')
>>> z = Variable('real', 'z')
>>> t = Variable('real', 't')
>>> a = Variable('real', 'a')
>>> b = Variable('real', 'b')
>>> body = [Assign(y,x+a)]
>>> translate = FunctionDef('translate', [x,y,a,b], [z,t], body)
>>> attributs = [x,y]
>>> methods = [translate]
>>> Point = ClassDef('Point', attributs, methods)
>>> incr = FunctionDef('incr', [x], [y], [Assign(y,x+1)])
>>> decr = FunctionDef('decr', [x], [y], [Assign(y,x-1)])
>>> Module('my_module', [], [incr, decr], [Point])
Module(my_module, [], [FunctionDef(incr, (x,), (y,), [y := 1 + x], [], [], None, False, function), FunctionDef(decr, (x,), (y,), [y := -1 + x], [], [], None, False, function)], [ClassDef(Point, (x, y), (FunctionDef(translate, (x, y, a, b), (z, t), [y := a + x], [], [], None, False, function),), [public])])
"""
def __new__(
cls,
name,
variables,
funcs,
interfaces=[],
classes=[],
imports=[],
):
if not isinstance(name, str):
raise TypeError('name must be a string')
if not iterable(variables):
raise TypeError('variables must be an iterable')
for i in variables:
if not isinstance(i, Variable):
raise TypeError('Only a Variable instance is allowed.')
if not iterable(funcs):
raise TypeError('funcs must be an iterable')
for i in funcs:
if not isinstance(i, FunctionDef):
raise TypeError('Only a FunctionDef instance is allowed.'
)
if not iterable(classes):
raise TypeError('classes must be an iterable')
for i in classes:
if not isinstance(i, ClassDef):
raise TypeError('Only a ClassDef instance is allowed.')
if not iterable(interfaces):
raise TypeError('interfaces must be an iterable')
for i in interfaces:
if not isinstance(i, Interface):
raise TypeError('Only a Inteface instance is allowed.')
if not iterable(imports):
raise TypeError('imports must be an iterable')
imports = list(imports)
for i in classes:
imports += i.imports
imports = set(imports) # for unicity
imports = Tuple(*imports)
return Basic.__new__(
cls,
name,
variables,
funcs,
interfaces,
classes,
imports,
)
@property
def name(self):
return self._args[0]
@property
def variables(self):
return self._args[1]
@property
def funcs(self):
return self._args[2]
@property
def interfaces(self):
return self._args[3]
@property
def classes(self):
return self._args[4]
@property
def imports(self):
return self._args[5]
@property
def declarations(self):
return [Declare(i.dtype, i) for i in self.variables]
@property
def body(self):
return self.interfaces + self.funcs + self.classes
[docs]class Program(Basic):
"""Represents a Program in the code. A block consists of the following inputs
variables: list
list of the variables that appear in the block.
declarations: list
list of declarations of the variables that appear in the block.
funcs: list
a list of FunctionDef instances
classes: list
a list of ClassDef instances
body: list
a list of statements
imports: list, tuple
list of needed imports
modules: list, tuple
list of needed modules
Examples
>>> from pyccel.ast.core import Variable, Assign
>>> from pyccel.ast.core import ClassDef, FunctionDef, Module
>>> x = Variable('real', 'x')
>>> y = Variable('real', 'y')
>>> z = Variable('real', 'z')
>>> t = Variable('real', 't')
>>> a = Variable('real', 'a')
>>> b = Variable('real', 'b')
>>> body = [Assign(y,x+a)]
>>> translate = FunctionDef('translate', [x,y,a,b], [z,t], body)
>>> attributs = [x,y]
>>> methods = [translate]
>>> Point = ClassDef('Point', attributs, methods)
>>> incr = FunctionDef('incr', [x], [y], [Assign(y,x+1)])
>>> decr = FunctionDef('decr', [x], [y], [Assign(y,x-1)])
>>> Module('my_module', [], [incr, decr], [Point])
Module(my_module, [], [FunctionDef(incr, (x,), (y,), [y := 1 + x], [], [], None, False, function), FunctionDef(decr, (x,), (y,), [y := -1 + x], [], [], None, False, function)], [ClassDef(Point, (x, y), (FunctionDef(translate, (x, y, a, b), (z, t), [y := a + x], [], [], None, False, function),), [public])])
"""
def __new__(
cls,
name,
variables,
funcs,
interfaces,
classes,
body,
imports=[],
modules=[],
):
if not isinstance(name, str):
raise TypeError('name must be a string')
if not iterable(variables):
raise TypeError('variables must be an iterable')
for i in variables:
if not isinstance(i, Variable):
raise TypeError('Only a Variable instance is allowed.')
if not iterable(funcs):
raise TypeError('funcs must be an iterable')
for i in funcs:
if not isinstance(i, FunctionDef):
raise TypeError('Only a FunctionDef instance is allowed.'
)
if not iterable(interfaces):
raise TypeError('interfaces must be an iterable')
for i in interfaces:
if not isinstance(i, Interface):
raise TypeError('Only a Interface instance is allowed.')
if not iterable(body):
raise TypeError('body must be an iterable')
if not iterable(classes):
raise TypeError('classes must be an iterable')
for i in classes:
if not isinstance(i, ClassDef):
raise TypeError('Only a ClassDef instance is allowed.')
if not iterable(imports):
raise TypeError('imports must be an iterable')
for i in funcs:
imports += i.imports
for i in classes:
imports += i.imports
imports = set(imports) # for unicity
imports = Tuple(*imports)
if not iterable(modules):
raise TypeError('modules must be an iterable')
# TODO
# elif isinstance(stmt, list):
# for s in stmt:
# body += printer(s) + "\n"
return Basic.__new__(
cls,
name,
variables,
funcs,
interfaces,
classes,
body,
imports,
modules,
)
@property
def name(self):
return self._args[0]
@property
def variables(self):
return self._args[1]
@property
def funcs(self):
return self._args[2]
@property
def interfaces(self):
return self._args[3]
@property
def classes(self):
return self._args[4]
@property
def body(self):
return self._args[5]
@property
def imports(self):
return self._args[6]
@property
def modules(self):
return self._args[7]
@property
def declarations(self):
return [Declare(i.dtype, i) for i in self.variables]
[docs]class For(Basic):
"""Represents a 'for-loop' in the code.
Expressions are of the form:
"for target in iter:
body..."
target : symbol
symbol representing the iterator
iter : iterable
iterable object. for the moment only Range is used
body : sympy expr
list of statements representing the body of the For statement.
Examples
>>> from sympy import symbols, MatrixSymbol
>>> from pyccel.ast.core import Assign, For
>>> i,b,e,s,x = symbols('i,b,e,s,x')
>>> A = MatrixSymbol('A', 1, 3)
>>> For(i, (b,e,s), [Assign(x,x-1), Assign(A[0, 1], x)])
For(i, Range(b, e, s), (x := x - 1, A[0, 1] := x))
"""
def __new__(
cls,
target,
iter,
body,
strict=True,
):
if strict:
target = sympify(target, locals=local_sympify)
cond_iter = iterable(iter)
cond_iter = cond_iter or isinstance(iter, (Range, Product,
Enumerate, Zip, Map))
cond_iter = cond_iter or isinstance(iter, Variable) \
and is_iterable_datatype(iter.dtype)
# cond_iter = cond_iter or isinstance(iter, ConstructorCall) \
# and is_iterable_datatype(iter.arguments[0].dtype)
if not cond_iter:
raise TypeError('iter must be an iterable')
if not iterable(body):
raise TypeError('body must be an iterable')
body = Tuple(*(sympify(i, locals=local_sympify) for i in
body))
return Basic.__new__(cls, target, iter, body)
@property
def target(self):
return self._args[0]
@property
def iterable(self):
return self._args[1]
@property
def body(self):
return self._args[2]
[docs] def insert2body(self, stmt):
self.body.append(stmt)
[docs]class FunctionalFor(Basic):
"""."""
def __new__(
cls,
loops,
target,
indexes,
index=None,
):
return Basic.__new__(cls, loops, target, indexes, index)
@property
def loops(self):
return self._args[0]
@property
def target(self):
return self._args[1]
@property
def indexes(self):
return self._args[2]
@property
def index(self):
return self._args[3]
[docs]class GeneratorComprehension(Basic):
pass
[docs]class FunctionalSum(FunctionalFor, GeneratorComprehension):
name = 'sum'
[docs]class FunctionalMax(FunctionalFor, GeneratorComprehension):
name = 'max'
[docs]class FunctionalMin(FunctionalFor, GeneratorComprehension):
name = 'min'
[docs]class FunctionalMap(FunctionalFor, GeneratorComprehension):
pass
[docs]class ForIterator(For):
"""Class that describes iterable classes defined by the user."""
def __new__(
cls,
target,
iter,
body,
strict=True,
):
if isinstance(iter, Symbol):
iter = Range(Len(iter))
return For.__new__(cls, target, iter, body, strict)
# TODO uncomment later when we intriduce iterators
# @property
# def target(self):
# ts = super(ForIterator, self).target
# if not(len(ts) == self.depth):
# raise ValueError('wrong number of targets')
# return ts
@property
def depth(self):
it = self.iterable
if isinstance(it, Variable):
if isinstance(it.dtype, NativeRange):
return 1
if isinstance(it.dtype, NativeTensor):
# TODO must be computed
return 2
cls_base = it.cls_base
if not cls_base:
raise TypeError('cls_base undefined')
methods = cls_base.methods_as_dict
it_method = methods['__iter__']
it_vars = []
for stmt in it_method.body:
if isinstance(stmt, Assign):
it_vars.append(stmt.lhs)
n = len(set([str(var.name) for var in it_vars]))
return n
else:
return 1
@property
def ranges(self):
return get_iterable_ranges(self.iterable)
# The following are defined to be sympy approved nodes. If there is something
# smaller that could be used, that would be preferable. We only use them as
# tokens.
[docs]class Is(Basic):
"""Represents a is expression in the code.
Examples
>>> from pyccel.ast import Is
>>> from pyccel.ast import Nil
>>> from sympy.abc import x
>>> Is(x, Nil())
Is(x, None)
"""
def __new__(cls, lhs, rhs):
return Basic.__new__(cls, lhs, rhs)
@property
def lhs(self):
return self._args[0]
@property
def rhs(self):
return self._args[1]
[docs]class ConstructorCall(AtomicExpr):
"""
It serves as a constructor for undefined function classes.
func: FunctionDef, str
an instance of FunctionDef or function name
arguments: list, tuple, None
a list of arguments.
kind: str
'function' or 'procedure'. default value: 'function'
"""
is_commutative = True
# TODO improve
def __new__(
cls,
func,
arguments,
cls_variable=None,
kind='function',
):
if not isinstance(func, (FunctionDef, Interface, str)):
raise TypeError('Expecting func to be a FunctionDef or str')
if isinstance(func, FunctionDef):
kind = func.kind
f_name = func.name
obj = Basic.__new__(cls, f_name)
obj._cls_variable = cls_variable
obj._kind = kind
obj._func = func
obj._arguments = arguments
return obj
def _sympystr(self, printer):
sstr = printer.doprint
name = sstr(self.name)
args = ''
if not self.arguments is None:
args = ', '.join(sstr(i) for i in self.arguments)
return '{0}({1})'.format(name, args)
@property
def func(self):
return self._func
@property
def kind(self):
return self._kind
@property
def arguments(self):
return self._arguments
@property
def cls_variable(self):
return self._cls_variable
@property
def name(self):
if isinstance(self.func, FunctionDef):
return self.func.name
else:
return self.func
[docs]class Nil(Basic):
"""
class for None object in the code.
"""
def _sympystr(self, printer):
sstr = printer.doprint
return sstr('None')
[docs]class VoidFunction(Basic):
#this class is used in order to eliminate certain atoms
# in an arithmitic expression so that we dont take them into
# consideration
def __new__(*args):
return Symbol("""x9846548484665
494794564465165161561""")
[docs]class Variable(Symbol):
"""Represents a typed variable.
dtype : str, DataType
The type of the variable. Can be either a DataType,
or a str (bool, int, real).
name : str, list, DottedName
The sympy object the variable represents. This can be either a string
or a dotted name, when using a Class attribut.
rank : int
used for arrays. [Default value: 0]
allocatable: False
used for arrays, if we need to allocate memory [Default value: False]
shape: int or list
shape of the array. [Default value: None]
cls_base: class
class base if variable is an object or an object member
Examples
>>> from sympy import symbols
>>> from pyccel.ast.core import Variable
>>> Variable('int', 'n')
n
>>> Variable('real', x, rank=2, shape=(n,2), allocatable=True)
x
>>> Variable('int', ('matrix', 'n_rows'))
matrix.n_rows
"""
def __new__(
cls,
dtype,
name,
rank=0,
allocatable=False,
is_pointer=False,
is_target=False,
is_polymorphic=None,
is_optional=None,
shape=None,
cls_base=None,
cls_parameters=None,
order='C',
precision=0
):
if isinstance(dtype, str) or str(dtype) == '*':
dtype = datatype(str(dtype))
elif not isinstance(dtype, DataType):
raise TypeError('datatype must be an instance of DataType.')
if allocatable is None:
allocatable = False
elif not isinstance(allocatable, bool):
raise TypeError('allocatable must be a boolean.')
if is_pointer is None:
is_pointer = False
elif not isinstance(is_pointer, bool):
raise TypeError('is_pointer must be a boolean.')
if is_target is None:
is_target = False
elif not isinstance(is_target, bool):
raise TypeError('is_target must be a boolean.')
if is_polymorphic is None:
if isinstance(dtype, CustomDataType):
is_polymorphic = dtype.is_polymorphic
else:
is_polymorphic = False
elif not isinstance(is_polymorphic, bool):
raise TypeError('is_polymorphic must be a boolean.')
if is_optional is None:
is_optional = False
elif not isinstance(is_optional, bool):
raise TypeError('is_optional must be a boolean.')
if not isinstance(precision,int):
raise TypeError('precision must be an integer.')
# if class attribut
if isinstance(name, str):
name = name.split(""".""")
if len(name) == 1:
name = name[0]
else:
name = DottedName(*name)
if not isinstance(name, (str, DottedName)):
raise TypeError('Expecting a string or DottedName, given {0}'.format(type(name)))
if not isinstance(rank, int):
raise TypeError('rank must be an instance of int.')
if rank == 0:
shape = ()
if not precision:
if isinstance(dtype, NativeInteger):
precision = 4
elif isinstance(dtype, (NativeReal, NativeComplex)):
precision = 8
# TODO improve order of arguments
obj = Basic.__new__(
cls,
dtype,
name,
rank,
allocatable,
shape,
cls_base,
cls_parameters,
is_pointer,
is_target,
is_polymorphic,
is_optional,
order,
precision,
)
assumptions = {}
class_type = cls_base \
or dtype.__class__.__name__.startswith('Pyccel')
alloweddtypes = (NativeBool, NativeRange, NativeString,
NativeSymbol, NativeGeneric)
if isinstance(dtype, NativeInteger):
assumptions['integer'] = True
elif isinstance(dtype, NativeReal):
assumptions['real'] = True
elif isinstance(dtype, NativeComplex):
assumptions['complex'] = True
elif not isinstance(dtype, alloweddtypes) and not class_type:
raise TypeError('Undefined datatype')
ass_copy = assumptions.copy()
obj._assumptions = StdFactKB(assumptions)
obj._assumptions._generator = ass_copy
return obj
@property
def dtype(self):
return self._args[0]
@property
def name(self):
return self._args[1]
@property
def rank(self):
return self._args[2]
@property
def allocatable(self):
return self._args[3]
@property
def shape(self):
return self._args[4]
@property
def cls_base(self):
return self._args[5]
@property
def cls_parameters(self):
return self._args[6]
@property
def is_pointer(self):
return self._args[7]
@property
def is_target(self):
return self._args[8]
@property
def is_polymorphic(self):
return self._args[9]
@property
def is_optional(self):
return self._args[10]
@property
def order(self):
return self._args[11]
@property
def precision(self):
return self._args[12]
@property
def is_ndarray(self):
"""user friendly method to check if the variable is an ndarray:
1. have a rank > 0
2. dtype is one among {int, bool, real, complex}
"""
if self.rank == 0:
return False
return isinstance(self.dtype, (NativeInteger, NativeBool,
NativeReal, NativeComplex))
def __str__(self):
if isinstance(self.name, (str, DottedName)):
return str(self.name)
elif self.name is iterable:
return """.""".join(str(n) for n in self.name)
def _sympystr(self, printer):
sstr = printer.doprint
if isinstance(self.name, (str, DottedName)):
return '{}'.format(sstr(self.name))
elif self.name is iterable:
return """.""".join(sstr(n) for n in self.name)
[docs] def inspect(self):
"""inspects the variable."""
print('>>> Variable')
print( ' name = {}'.format(self.name))
print( ' dtype = {}'.format(self.dtype))
print( ' rank = {}'.format(self.rank))
print( ' allocatable = {}'.format(self.allocatable))
print( ' shape = {}'.format(self.shape))
print( ' cls_base = {}'.format(self.cls_base))
print( ' cls_parameters = {}'.format(self.cls_parameters))
print( ' is_pointer = {}'.format(self.is_pointer))
print( ' is_target = {}'.format(self.is_target))
print( ' is_polymorphic = {}'.format(self.is_polymorphic))
print( ' is_optional = {}'.format(self.is_optional))
print( '<<<')
[docs] def clone(self, name):
# TODO check it is up to date
cls = eval(self.__class__.__name__)
return cls(
self.dtype,
name,
rank=self.rank,
allocatable=self.allocatable,
shape=self.shape,
is_pointer=self.is_pointer,
is_target=self.is_target,
is_polymorphic=self.is_polymorphic,
is_optional=self.is_optional,
cls_base=self.cls_base,
cls_parameters=self.cls_parameters,
)
def __getnewargs__(self):
"""used for Pickling self."""
args = (
self.dtype,
self.name,
self.rank,
self.allocatable,
self.is_pointer,
self.is_polymorphic,
self.is_optional,
self.shape,
self.cls_base,
self.cls_parameters,
)
return args
def _eval_subs(self, old, new):
return self
def _eval_is_positive(self):
#we do this inorder to infere the type of Pow expression correctly
return self.is_real
[docs]class DottedVariable(AtomicExpr, Boolean):
"""
Represents a dotted variable.
"""
def __new__(cls, *args):
if not isinstance(args[0], (
Variable,
Symbol,
IndexedVariable,
IndexedElement,
IndexedBase,
Indexed,
Function,
DottedVariable,
)):
raise TypeError('Expecting a Variable or a function call, got instead {0} of type {1}'.format(str(args[0]),
type(args[0])))
if not isinstance(args[1], (
Variable,
Symbol,
IndexedVariable,
IndexedElement,
IndexedBase,
Indexed,
Function,
)):
raise TypeError('Expecting a Variable or a function call, got instead {0} of type {1}'.format(str(args[1]),
type(args[1])))
obj = Basic.__new__(cls, args[0], args[1])
assumptions = {}
if isinstance(args[1], (Variable, IndexedVariable,
IndexedElement)):
dtype = args[1].dtype
class_type = isinstance(args[1], Variable) \
and args[1].cls_base \
or dtype.__class__.__name__.startswith('Pyccel')
alloweddtypes = (NativeBool, NativeRange, NativeString)
if isinstance(dtype, NativeInteger) or args[1].is_integer:
assumptions['integer'] = True
elif isinstance(dtype, NativeReal) \
or args[1].is_real:
assumptions['real'] = True
elif isinstance(dtype, NativeComplex) or args[1].is_complex:
assumptions['complex'] = True
elif not isinstance(dtype, alloweddtypes) \
and not class_type:
raise TypeError('Undefined datatype')
ass_copy = assumptions.copy()
obj._assumptions = StdFactKB(assumptions)
obj._assumptions._generator = ass_copy
return obj
@property
def lhs(self):
return self._args[0]
@property
def rhs(self):
return self._args[1]
@property
def rank(self):
return self._args[1].rank
@property
def dtype(self):
return self._args[1].dtype
@property
def name(self):
if isinstance(self.lhs, DottedVariable):
name_0 = self.lhs.name
else:
name_0 = str(self.lhs)
if isinstance(self.rhs, Function):
name_1 = str(type(self.rhs).__name__)
elif isinstance(self.rhs, Symbol):
name_1 = self.rhs.name
else:
name_1 = str(self.rhs)
return name_0 + """.""" + name_1
def __str__(self):
return self.name
def _sympystr(self, Printer):
return self.name
@property
def cls_base(self):
return self._args[1].cls_base
@property
def names(self):
"""Return list of names as strings."""
ls = []
for i in [self.lhs, self.rhs]:
if not isinstance(i, DottedVariable):
ls.append(str(i))
else:
ls += i.names
return ls
def _eval_subs(self, old, new):
return self
[docs]class ValuedVariable(Variable):
"""Represents a valued variable in the code.
variable: Variable
A single variable
value: Variable, or instance of Native types
value associated to the variable
Examples
>>> from pyccel.ast.core import ValuedVariable
>>> n = ValuedVariable('int', 'n', value=4)
>>> n
n := 4
"""
def __new__(cls, *args, **kwargs):
# if value is not given, we set it to Nil
# we also remove value from kwargs,
# since it is not a valid argument for Variable
value = kwargs.pop('value', Nil())
obj = Variable.__new__(cls, *args, **kwargs)
obj._value = value
return obj
@property
def value(self):
return self._value
def _sympystr(self, printer):
sstr = printer.doprint
name = sstr(self.name)
value = sstr(self.value)
return '{0}={1}'.format(name, value)
[docs]class Constant(ValuedVariable):
"""
Examples
"""
pass
[docs]class Argument(Symbol):
"""An abstract Argument data structure.
Examples
>>> from pyccel.ast.core import Argument
>>> n = Argument('n')
>>> n
n
"""
pass
[docs]class ValuedArgument(Basic):
"""Represents a valued argument in the code.
Examples
>>> from pyccel.ast.core import ValuedArgument
>>> n = ValuedArgument('n', 4)
>>> n
n=4
"""
def __new__(cls, expr, value):
if isinstance(expr, str):
expr = Symbol(expr)
# TODO should we turn back to Argument
if not isinstance(expr, Symbol):
raise TypeError('Expecting an argument')
return Basic.__new__(cls, expr, value)
@property
def argument(self):
return self._args[0]
@property
def value(self):
return self._args[1]
@property
def name(self):
return self.argument.name
def _sympystr(self, printer):
sstr = printer.doprint
argument = sstr(self.argument)
value = sstr(self.value)
return '{0}={1}'.format(argument, value)
[docs]class FunctionCall(Basic):
"""Represents a function call in the code.
"""
def __new__(cls, func, args):
# ...
if not isinstance(func, (str, FunctionDef, Function)):
raise TypeError('> expecting a str, FunctionDef, Function')
if isinstance(func, FunctionDef):
func = func.name
# ...
# ...
if not isinstance(args, (tuple, list, Tuple)):
raise TypeError('> expecting an iterable')
args = Tuple(*args)
# ...
return Basic.__new__(cls, func, args)
@property
def func(self):
return self._args[0]
@property
def arguments(self):
return self._args[1]
[docs]class Return(Basic):
"""Represents a function return in the code.
expr : sympy expr
The expression to return.
stmts :represent assign stmts in the case of expression return
"""
def __new__(cls, expr, stmt=None):
if stmt and not isinstance(stmt, (Assign, CodeBlock)):
raise TypeError('stmt should only be of type Assign')
return Basic.__new__(cls, expr, stmt)
@property
def expr(self):
return self._args[0]
@property
def stmt(self):
return self._args[1]
def __getnewargs__(self):
"""used for Pickling self."""
args = (self.expr, self.stmt)
return args
[docs]class Interface(Basic):
"""Represent an Interface"""
def __new__(
cls,
name,
functions,
hide=False,
):
if not isinstance(name, str):
raise TypeError('Expecting an str')
if not isinstance(functions, list):
raise TypeError('Expecting a list')
return Basic.__new__(cls, name, functions, hide)
@property
def name(self):
return self._args[0]
@property
def functions(self):
return self._args[1]
@property
def hide(self):
return self.functions[0].hide or self._args[2]
@property
def global_vars(self):
return self.functions[0].global_vars
@property
def cls_name(self):
return self.functions[0].cls_name
@property
def kind(self):
return self.functions[0].kind
@property
def imports(self):
return self.functions[0].imports
@property
def decorators(self):
return self.functions[0].decorators
@property
def is_procedure(self):
return self.functions[0].is_procedure
[docs] def rename(self, newname):
return Interface(newname, self.functions)
[docs]class FunctionDef(Basic):
"""Represents a function definition.
name : str
The name of the function.
arguments : iterable
The arguments to the function.
results : iterable
The direct outputs of the function.
body : iterable
The body of the function.
local_vars : list of Symbols
These are used internally by the routine.
global_vars : list of Symbols
Variables which will not be passed into the function.
cls_name: str
Class name if the function is a method of cls_name
hide: bool
if True, the function definition will not be generated.
kind: str
'function' or 'procedure'. default value: 'function'
is_static: bool
True for static functions. Needed for f2py
imports: list, tuple
a list of needed imports
decorators: list, tuple
a list of proporties
Examples
>>> from pyccel.ast.core import Assign, Variable, FunctionDef
>>> x = Variable('real', 'x')
>>> y = Variable('real', 'y')
>>> args = [x]
>>> results = [y]
>>> body = [Assign(y,x+1)]
>>> FunctionDef('incr', args, results, body)
FunctionDef(incr, (x,), (y,), [y := 1 + x], [], [], None, False, function)
One can also use parametrized argument, using ValuedArgument
>>> from pyccel.ast.core import Variable
>>> from pyccel.ast.core import Assign
>>> from pyccel.ast.core import FunctionDef
>>> from pyccel.ast.core import ValuedArgument
>>> from pyccel.ast.core import GetDefaultFunctionArg
>>> n = ValuedArgument('n', 4)
>>> x = Variable('real', 'x')
>>> y = Variable('real', 'y')
>>> args = [x, n]
>>> results = [y]
>>> body = [Assign(y,x+n)]
>>> FunctionDef('incr', args, results, body)
FunctionDef(incr, (x, n=4), (y,), [y := 1 + x], [], [], None, False, function, [])
"""
def __new__(
cls,
name,
arguments,
results,
body,
local_vars=[],
global_vars=[],
cls_name=None,
hide=False,
kind='function',
is_static=False,
imports=[],
decorators={},
header=None,
is_recursive=False,
):
# name
if isinstance(name, str):
name = Symbol(name)
elif isinstance(name, (tuple, list)):
name_ = []
for i in name:
if isinstance(i, str):
name = name + Symbol(i)
elif not isinstance(i, Symbol):
raise TypeError('Function name must be Symbol or string'
)
name = tuple(name_)
elif not isinstance(name, Symbol):
raise TypeError('Function name must be Symbol or string')
# arguments
if not iterable(arguments):
raise TypeError('arguments must be an iterable')
# TODO improve and uncomment
# if not all(isinstance(a, Argument) for a in arguments):
# raise TypeError("All arguments must be of type Argument")
arguments = Tuple(*arguments)
# body
if not iterable(body):
raise TypeError('body must be an iterable')
# body = Tuple(*(i for i in body))
# results
if not iterable(results):
raise TypeError('results must be an iterable')
results = Tuple(*results)
# if method
if cls_name:
if not isinstance(cls_name, str):
raise TypeError('cls_name must be a string')
# if not cls_variable:
# raise TypeError('Expecting a instance of {0}'.format(cls_name))
if kind is None:
kind = 'function'
if not isinstance(kind, str):
raise TypeError('Expecting a string for kind.')
if not isinstance(is_static, bool):
raise TypeError('Expecting a boolean for is_static attribut'
)
if not kind in ['function', 'procedure']:
raise ValueError("kind must be one among {'function', 'procedure'}"
)
if not iterable(imports):
raise TypeError('imports must be an iterable')
if not isinstance(decorators, dict):
raise TypeError('decorators must be a dict')
return Basic.__new__(
cls,
name,
arguments,
results,
body,
local_vars,
global_vars,
cls_name,
hide,
kind,
is_static,
imports,
decorators,
header,
is_recursive,
)
@property
def name(self):
return self._args[0]
@property
def arguments(self):
return self._args[1]
@property
def results(self):
return self._args[2]
@property
def body(self):
return self._args[3]
@property
def local_vars(self):
return self._args[4]
@property
def global_vars(self):
return self._args[5]
@property
def cls_name(self):
return self._args[6]
@property
def hide(self):
return self._args[7]
@property
def kind(self):
return self._args[8]
@property
def is_static(self):
return self._args[9]
@property
def imports(self):
return self._args[10]
@property
def decorators(self):
return self._args[11]
@property
def header(self):
return self._args[12]
@property
def is_recursive(self):
return self._args[13]
[docs] def print_body(self):
for s in self.body:
print(s)
[docs] def set_recursive(self):
return FunctionDef(
self.name,
self.arguments,
self.results,
self.body,
local_vars=self.local_vars,
global_vars=self.global_vars,
cls_name=self.cls_name,
hide=self.hide,
kind=self.kind,
is_static=self.is_static,
header=self.header,
imports = self.imports,
decorators = self.decorators,
is_recursive=True,
)
[docs] def rename(self, newname):
"""
Rename the FunctionDef name by creating a new FunctionDef with
newname.
newname: str
new name for the FunctionDef
"""
return FunctionDef(
newname,
self.arguments,
self.results,
self.body,
local_vars=self.local_vars,
global_vars=self.global_vars,
cls_name=self.cls_name,
hide=self.hide,
kind=self.kind,
is_static=self.is_static,
header=self.header,
imports = self.imports,
decorators = self.decorators,
is_recursive=self.is_recursive,
)
[docs] def vectorize(self, body , header):
""" return vectorized FunctionDef """
decorators = self.decorators
decorators.pop('vectorize')
return FunctionDef(
'vec_'+str(self.name),
self.arguments,
[],
body,
local_vars=self.local_vars,
global_vars=self.global_vars,
cls_name=self.cls_name,
hide=self.hide,
kind='procedure',
is_static=self.is_static,
header=header,
imports = self.imports,
decorators = decorators,
is_recursive=self.is_recursive)
@property
def is_procedure(self):
"""Returns True if a procedure."""
flag = False
if len(self.results) == 1 and isinstance(self.results[0], Expr):
vars_ = [i for i in preorder_traversal(self.results)
if isinstance(i, Variable)]
flag = flag or any([i.allocatable or i.rank > 0 for i in
vars_])
else:
flag = flag or len(self.results) == 1 \
and self.results[0].allocatable
flag = flag or len(self.results) == 1 \
and self.results[0].rank > 0
flag = flag or len(self.results) > 1
flag = flag or len(self.results) == 0
flag = flag or self.kind == 'procedure' or self.is_static
flag = flag \
or len(set(self.results).intersection(self.arguments)) > 0
return flag
def __getnewargs__(self):
"""used for Pickling self."""
args = (
self.name,
self.arguments,
self.results,
self.body,
self.local_vars,
self.global_vars,
self.cls_name,
self.hide,
self.kind,
self.is_static,
self.imports,
self.decorators,
self.header,
self.is_recursive,
)
return args
[docs]class SympyFunction(FunctionDef):
"""Represents a function definition."""
[docs] def rename(self, newname):
"""
Rename the SympyFunction name by creating a new SympyFunction with
newname.
newname: str
new name for the SympyFunction
"""
return SympyFunction(newname, self.arguments, self.results,
self.body, cls_name=self.cls_name)
[docs]class PythonFunction(FunctionDef):
"""Represents a Python-Function definition."""
[docs] def rename(self, newname):
"""
Rename the PythonFunction name by creating a new PythonFunction with
newname.
newname: str
new name for the PythonFunction
"""
return PythonFunction(newname, self.arguments, self.results,
self.body, cls_name=self.cls_name)
[docs]class GetDefaultFunctionArg(Basic):
"""Creates a FunctionDef for handling optional arguments in the code.
arg: ValuedArgument, ValuedVariable
argument for which we want to create the function returning the default
value
func: FunctionDef
the function/subroutine in which the optional arg is used
Examples
>>> from pyccel.ast.core import Variable
>>> from pyccel.ast.core import Assign
>>> from pyccel.ast.core import FunctionDef
>>> from pyccel.ast.core import ValuedArgument
>>> from pyccel.ast.core import GetDefaultFunctionArg
>>> n = ValuedArgument('n', 4)
>>> x = Variable('real', 'x')
>>> y = Variable('real', 'y')
>>> args = [x, n]
>>> results = [y]
>>> body = [Assign(y,x+n)]
>>> incr = FunctionDef('incr', args, results, body)
>>> get_n = GetDefaultFunctionArg(n, incr)
>>> get_n.name
get_default_incr_n
>>> get_n
get_default_incr_n(n=4)
You can also use **ValuedVariable** as in the following example
>>> from pyccel.ast.core import ValuedVariable
>>> n = ValuedVariable('int', 'n', value=4)
>>> x = Variable('real', 'x')
>>> y = Variable('real', 'y')
>>> args = [x, n]
>>> results = [y]
>>> body = [Assign(y,x+n)]
>>> incr = FunctionDef('incr', args, results, body)
>>> get_n = GetDefaultFunctionArg(n, incr)
>>> get_n
get_default_incr_n(n=4)
"""
def __new__(cls, arg, func):
if not isinstance(arg, (ValuedArgument, ValuedVariable)):
raise TypeError('Expecting a ValuedArgument or ValuedVariable'
)
if not isinstance(func, FunctionDef):
raise TypeError('Expecting a FunctionDef')
return Basic.__new__(cls, arg, func)
@property
def argument(self):
return self._args[0]
@property
def func(self):
return self._args[1]
@property
def name(self):
text = \
'get_default_{func}_{arg}'.format(arg=self.argument.name,
func=self.func.name)
return text
def _sympystr(self, printer):
sstr = printer.doprint
name = sstr(self.name)
argument = sstr(self.argument)
return '{0}({1})'.format(name, argument)
[docs]class ClassDef(Basic):
"""Represents a class definition.
name : str
The name of the class.
attributs: iterable
The attributs to the class.
methods: iterable
Class methods
options: list, tuple
list of options ('public', 'private', 'abstract')
imports: list, tuple
list of needed imports
parent : str
parent's class name
Examples
>>> from pyccel.ast.core import Variable, Assign
>>> from pyccel.ast.core import ClassDef, FunctionDef
>>> x = Variable('real', 'x')
>>> y = Variable('real', 'y')
>>> z = Variable('real', 'z')
>>> t = Variable('real', 't')
>>> a = Variable('real', 'a')
>>> b = Variable('real', 'b')
>>> body = [Assign(y,x+a)]
>>> translate = FunctionDef('translate', [x,y,a,b], [z,t], body)
>>> attributs = [x,y]
>>> methods = [translate]
>>> ClassDef('Point', attributs, methods)
ClassDef(Point, (x, y), (FunctionDef(translate, (x, y, a, b), (z, t), [y := a + x], [], [], None, False, function),), [public])
"""
def __new__(
cls,
name,
attributes=[],
methods=[],
options=['public'],
imports=[],
parent=[],
interfaces=[],
):
# name
if isinstance(name, str):
name = Symbol(name)
elif not isinstance(name, Symbol):
raise TypeError('Function name must be Symbol or string')
# attributs
if not iterable(attributes):
raise TypeError('attributs must be an iterable')
attributes = Tuple(*attributes)
# methods
if not iterable(methods):
raise TypeError('methods must be an iterable')
# options
if not iterable(options):
raise TypeError('options must be an iterable')
# imports
if not iterable(imports):
raise TypeError('imports must be an iterable')
if not iterable(parent):
raise TypeError('parent must be iterable')
if not iterable(interfaces):
raise TypeError('interfaces must be iterable')
imports = list(imports)
for i in methods:
imports += list(i.imports)
imports = set(imports) # for unicity
imports = Tuple(*imports)
# ...
# look if the class has the method __del__
# d_methods = {}
# for i in methods:
# d_methods[str(i.name).replace('\'','')] = i
# if not ('__del__' in d_methods):
# dtype = DataTypeFactory(str(name), ("_name"), prefix='Custom')
# this = Variable(dtype(), 'self')
# constructs the __del__ method if not provided
# args = []
# for a in attributs:
# if isinstance(a, Variable):
# if a.allocatable:
# args.append(a)
# args = [Variable(a.dtype, DottedName(str(this), str(a.name))) for a in args]
# body = [Del(a) for a in args]
# free = FunctionDef('__del__', [this], [], \
# body, local_vars=[], global_vars=[], \
# cls_name='__UNDEFINED__', kind='procedure', imports=[])
# methods = list(methods) + [free]
# TODO move this somewhere else
methods = Tuple(*methods)
# ...
return Basic.__new__(
cls,
name,
attributes,
methods,
options,
imports,
parent,
interfaces,
)
@property
def name(self):
return self._args[0]
@property
def attributes(self):
return self._args[1]
@property
def methods(self):
return self._args[2]
@property
def options(self):
return self._args[3]
@property
def imports(self):
return self._args[4]
@property
def parent(self):
return self._args[5]
@property
def interfaces(self):
return self._args[6]
@property
def methods_as_dict(self):
"""Returns a dictionary that contains all methods, where the key is the
method's name."""
d_methods = {}
for i in self.methods:
d_methods[str(i.name)] = i
return d_methods
@property
def attributes_as_dict(self):
"""Returns a dictionary that contains all attributs, where the key is the
attribut's name."""
d_attributes = {}
for i in self.attributes:
d_attributes[str(i.name)] = i
return d_attributes
# TODO add other attributes?
[docs] def get_attribute(self, O, attr):
"""Returns the attribute attr of the class O of instance self."""
if not isinstance(attr, str):
raise TypeError('Expecting attribute to be a string')
if isinstance(O, Variable):
cls_name = str(O.name)
else:
cls_name = str(O)
attributes = {}
for i in self.attributes:
attributes[str(i.name)] = i
if not attr in attributes:
raise ValueError('{0} is not an attribut of {1}'.format(attr,
str(self)))
var = attributes[attr]
name = DottedName(cls_name, str(var.name))
return Variable(
var.dtype,
name,
rank=var.rank,
allocatable=var.allocatable,
shape=var.shape,
cls_base=var.cls_base,
)
@property
def is_iterable(self):
"""Returns True if the class has an iterator."""
names = [str(m.name) for m in self.methods]
if '__next__' in names and '__iter__' in names:
return True
elif '__next__' in names:
raise ValueError('ClassDef does not contain __iter__ method')
elif '__iter__' in names:
raise ValueError('ClassDef does not contain __next__ method')
else:
return False
@property
def is_with_construct(self):
"""Returns True if the class is a with construct."""
names = [str(m.name) for m in self.methods]
if '__enter__' in names and '__exit__' in names:
return True
elif '__enter__' in names:
raise ValueError('ClassDef does not contain __exit__ method')
elif '__exit__' in names:
raise ValueError('ClassDef does not contain __enter__ method')
else:
return False
@property
def hide(self):
if 'hide' in self.options:
return True
else:
return self.is_iterable or self.is_with_construct
def _eval_subs(self, old , new):
return self
[docs]class Import(Basic):
"""Represents inclusion of dependencies in the code.
target : str, list, tuple, Tuple
targets to import
Examples
>>> from pyccel.ast.core import Import
>>> from pyccel.ast.core import DottedName
>>> Import('foo')
import foo
>>> abc = DottedName('foo', 'bar', 'baz')
>>> Import(abc)
import foo.bar.baz
>>> Import(['foo', abc])
import foo, foo.bar.baz
"""
def __new__(cls, target, source=None):
def _format(i):
if isinstance(i, str):
if '.' in i:
return DottedName(*i.split('.'))
else:
return Symbol(i)
if isinstance(i, (DottedName, AsName)):
return i
elif isinstance(i, Symbol):
return i
else:
raise TypeError('Expecting a string, Symbol DottedName, given {}'.format(type(i)))
_target = []
if isinstance(target, (str, Symbol, DottedName, AsName)):
_target = [_format(target)]
elif iterable(target):
for i in target:
_target.append(_format(i))
target = Tuple(*_target)
if not source is None:
source = _format(source)
return Basic.__new__(cls, target, source)
@property
def target(self):
return self._args[0]
@property
def source(self):
return self._args[1]
def _sympystr(self, printer):
sstr = printer.doprint
target = ', '.join([sstr(i) for i in self.target])
if self.source is None:
return 'import {target}'.format(target=target)
else:
source = sstr(self.source)
return 'from {source} import {target}'.format(source=source,
target=target)
[docs]class TupleImport(Basic):
def __new__(cls, *args):
for a in args:
if not isinstance(a, Import):
raise TypeError('Expecting an Import statement')
return Basic.__new__(cls, *args)
@property
def imports(self):
return self._args
def _sympystr(self, printer):
sstr = printer.doprint
return '\n'.join(sstr(n) for n in self.imports)
[docs]class Load(Basic):
"""Similar to 'importlib' in python. In addition, we can also provide the
functions we want to import.
module: str, DottedName
name of the module to load.
funcs: str, list, tuple, Tuple
a string representing the function to load, or a list of strings.
as_lambda: bool
load as a Lambda expression, if True
nargs: int
number of arguments of the function to load. (default = 1)
Examples
>>> from pyccel.ast.core import Load
"""
def __new__(
cls,
module,
funcs=None,
as_lambda=False,
nargs=1,
):
if not isinstance(module, (str, DottedName, list, tuple,
Tuple)):
raise TypeError('Expecting a string or DottedName, given {0}'.format(type(module)))
# see syntax
if isinstance(module, str):
module = module.replace('__', """.""")
if isinstance(module, (list, tuple, Tuple)):
module = DottedName(*module)
if funcs:
if not isinstance(funcs, (str, DottedName, list, tuple,
Tuple)):
raise TypeError('Expecting a string or DottedName')
if isinstance(funcs, str):
funcs = [funcs]
elif not isinstance(funcs, (list, tuple, Tuple)):
raise TypeError('Expecting a string, list, tuple, Tuple')
if not isinstance(as_lambda, (BooleanTrue, BooleanFalse, bool)):
raise TypeError('Expecting a boolean, given {0}'.format(as_lambda))
return Basic.__new__(cls, module, funcs, as_lambda, nargs)
@property
def module(self):
return self._args[0]
@property
def funcs(self):
return self._args[1]
@property
def as_lambda(self):
return self._args[2]
@property
def nargs(self):
return self._args[3]
[docs] def execute(self):
module = str(self.module)
try:
package = importlib.import_module(module)
except:
raise ImportError('could not import {0}'.format(module))
ls = []
for f in self.funcs:
try:
m = getattr(package, '{0}'.format(str(f)))
except:
raise ImportError('could not import {0}'.format(f))
# TODO improve
if self.as_lambda:
args = []
for i in range(0, self.nargs):
fi = Symbol('f{0}'.format(i))
args.append(fi)
if len(args) == 1:
arg = args[0]
m = Lambda(arg, m(arg, evaluate=False))
else:
m = Lambda(args, m(evaluate=False, *args))
ls.append(m)
return ls
# TODO: Should Declare have an optional init value for each var?
[docs]class Declare(Basic):
"""Represents a variable declaration in the code.
dtype : DataType
The type for the declaration.
variable(s)
A single variable or an iterable of Variables. If iterable, all
Variables must be of the same type.
intent: None, str
one among {'in', 'out', 'inout'}
value: Expr
variable value
static: bool
True for a static declaration of an array.
Examples
>>> from pyccel.ast.core import Declare, Variable
>>> Declare('int', Variable('int', 'n'))
Declare(NativeInteger(), (n,), None)
>>> Declare('real', Variable('real', 'x'), intent='out')
Declare(NativeReal(), (x,), out)
"""
def __new__(
cls,
dtype,
variable,
intent=None,
value=None,
static=False,
):
if isinstance(dtype, str):
dtype = datatype(dtype)
elif not isinstance(dtype, DataType):
raise TypeError('datatype must be an instance of DataType.')
if not isinstance(variable, Variable):
raise TypeError('var must be of type Variable, given {0}'.format(variable))
if variable.dtype != dtype:
raise ValueError('All variables must have the same dtype')
if intent:
if not intent in ['in', 'out', 'inout']:
raise ValueError("intent must be one among {'in', 'out', 'inout'}")
if not isinstance(static, bool):
raise TypeError('Expecting a boolean for static attribut')
return Basic.__new__(
cls,
dtype,
variable,
intent,
value,
static,
)
@property
def dtype(self):
return self._args[0]
@property
def variable(self):
return self._args[1]
@property
def intent(self):
return self._args[2]
@property
def value(self):
return self._args[3]
@property
def static(self):
return self._args[4]
[docs]class Subroutine(UndefinedFunction):
pass
[docs]class Break(Basic):
"""Represents a break in the code."""
pass
[docs]class Continue(Basic):
"""Represents a continue in the code."""
pass
[docs]class Raise(Basic):
"""Represents a raise in the code."""
pass
# TODO: improve with __new__ from Function and add example
[docs]class Random(Function):
"""
Represents a 'random' number in the code.
"""
# TODO : remove later
def __str__(self):
return 'random'
def __new__(cls, seed):
return Basic.__new__(cls, seed)
@property
def seed(self):
return self._args[0]
# TODO: improve with __new__ from Function and add example
[docs]class SumFunction(Basic):
"""Represents a Sympy Sum Function.
body: Expr
Sympy Expr in which the sum will be performed.
iterator:
a tuple that containts the index of the sum and it's range.
"""
def __new__(
cls,
body,
iterator,
stmts=None,
):
if not isinstance(iterator, (tuple, Tuple)):
raise TypeError('iterator must be a tuple')
if not len(iterator) == 3:
raise ValueError('iterator must be of lenght 3')
return Basic.__new__(cls, body, iterator, stmts)
@property
def body(self):
return self._args[0]
@property
def iterator(self):
return self._args[1]
@property
def stmts(self):
return self._args[2]
[docs]class Len(Function):
"""
Represents a 'len' expression in the code.
"""
def __new__(cls, arg):
obj = Basic.__new__(cls, arg)
assumptions = {'integer': True}
ass_copy = assumptions.copy()
obj._assumptions = StdFactKB(assumptions)
obj._assumptions._generator = ass_copy
return obj
@property
def arg(self):
return self._args[0]
@property
def dtype(self):
return 'int'
# TODO - add examples
[docs]class ZerosLike(Function):
"""Represents variable assignment using numpy.zeros_like for code generation.
lhs : Expr
Sympy object representing the lhs of the expression. These should be
singular objects, such as one would use in writing code. Notable types
include Symbol, MatrixSymbol, MatrixElement, and Indexed. Types that
subclass these types are also supported.
rhs : Variable
the input variable
Examples
>>> from sympy import symbols
>>> from pyccel.ast.core import Zeros, ZerosLike
>>> n,m,x = symbols('n,m,x')
>>> y = Zeros(x, (n,m))
>>> z = ZerosLike(y)
"""
# TODO improve in the spirit of assign
def __new__(cls, rhs=None, lhs=None):
if isinstance(lhs, str):
lhs = Symbol(lhs)
# Tuple of things that can be on the lhs of an assignment
assignable = (
Symbol,
MatrixSymbol,
MatrixElement,
Indexed,
Idx,
Variable,
)
if lhs and not isinstance(lhs, assignable):
raise TypeError('Cannot assign to lhs of type %s.'
% type(lhs))
return Basic.__new__(cls, lhs, rhs)
def _sympystr(self, printer):
sstr = printer.doprint
return '{0} := 0'.format(sstr(self.lhs))
@property
def lhs(self):
return self._args[0]
@property
def rhs(self):
return self._args[1]
@property
def init_value(self):
def _native_init_value(dtype):
if isinstance(dtype, NativeInteger):
return 0
elif isinstance(dtype, NativeReal):
return 0.0
elif isinstance(dtype, NativeComplex):
return 0.0
elif isinstance(dtype, NativeBool):
return BooleanFalse()
raise TypeError('Expecting a Native type, given {}'.format(dtype))
_native_types = (NativeInteger, NativeReal,
NativeComplex, NativeBool)
rhs = self.rhs
if isinstance(rhs.dtype, _native_types):
return _native_init_value(rhs.dtype)
elif isinstance(rhs, (Variable, IndexedVariable)):
return _native_init_value(rhs.dtype)
elif isinstance(rhs, IndexedElement):
return _native_init_value(rhs.base.dtype)
else:
raise TypeError('Unknown type for {name}, given {dtype}'.format(dtype=type(rhs),
name=rhs))
[docs]class Print(Basic):
"""Represents a print function in the code.
expr : sympy expr
The expression to return.
Examples
>>> from sympy import symbols
>>> from pyccel.ast.core import Print
>>> n,m = symbols('n,m')
>>> Print(('results', n,m))
Print((results, n, m))
"""
def __new__(cls, expr):
if not isinstance(expr, list):
expr = sympify(expr, locals=local_sympify)
return Basic.__new__(cls, expr)
@property
def expr(self):
return self._args[0]
[docs]class SymbolicPrint(Basic):
"""Represents a print function of symbolic expressions in the code.
expr : sympy expr
The expression to return.
Examples
>>> from sympy import symbols
>>> from pyccel.ast.core import Print
>>> n,m = symbols('n,m')
>>> Print(('results', n,m))
Print((results, n, m))
"""
def __new__(cls, expr):
if not iterable(expr):
raise TypeError('Expecting an iterable')
for i in expr:
if not isinstance(i, (Lambda, SymbolicAssign,
SympyFunction)):
raise TypeError('Expecting Lambda, SymbolicAssign, SympyFunction for {}'.format(i))
return Basic.__new__(cls, expr)
@property
def expr(self):
return self._args[0]
[docs]class Del(Basic):
"""Represents a memory deallocation in the code.
variables : list, tuple
a list of pyccel variables
Examples
>>> from pyccel.ast.core import Del, Variable
>>> x = Variable('real', 'x', rank=2, shape=(10,2), allocatable=True)
>>> Del([x])
Del([x])
"""
def __new__(cls, expr):
# TODO: check that the variable is allocatable
if not iterable(expr):
expr = Tuple(expr)
return Basic.__new__(cls, expr)
@property
def variables(self):
return self._args[0]
[docs]class EmptyLine(Basic):
"""Represents a EmptyLine in the code.
text : str
the comment line
Examples
>>> from pyccel.ast.core import EmptyLine
>>> EmptyLine()
"""
def __new__(cls):
return Basic.__new__(cls)
def _sympystr(self, printer):
return ''
[docs]class NewLine(Basic):
"""Represents a NewLine in the code.
text : str
the comment line
Examples
>>> from pyccel.ast.core import NewLine
>>> NewLine()
"""
def __new__(cls):
return Basic.__new__(cls)
def _sympystr(self, printer):
return '\n'
[docs]class IndexedVariable(IndexedBase):
"""
Represents an indexed variable, like x in x[i], in the code.
Examples
>>> from sympy import symbols, Idx
>>> from pyccel.ast.core import IndexedVariable
>>> A = IndexedVariable('A'); A
A
>>> type(A)
<class 'pyccel.ast.core.IndexedVariable'>
When an IndexedVariable object receives indices, it returns an array with named
axes, represented by an IndexedElement object:
>>> i, j = symbols('i j', integer=True)
>>> A[i, j, 2]
A[i, j, 2]
>>> type(A[i, j, 2])
<class 'pyccel.ast.core.IndexedElement'>
The IndexedVariable constructor takes an optional shape argument. If given,
it overrides any shape information in the indices. (But not the index
ranges!)
>>> m, n, o, p = symbols('m n o p', integer=True)
>>> i = Idx('i', m)
>>> j = Idx('j', n)
>>> A[i, j].shape
(m, n)
>>> B = IndexedVariable('B', shape=(o, p))
>>> B[i, j].shape
(m, n)
**todo:** fix bug. the last result must be : (o,p)
"""
def __new__(
cls,
label,
shape=None,
dtype=None,
prec=0,
order=None,
rank = 0,
**kw_args
):
if dtype is None:
raise TypeError('datatype must be provided')
if isinstance(dtype, str):
dtype = datatype(dtype)
elif not isinstance(dtype, DataType):
raise TypeError('datatype must be an instance of DataType.')
obj = IndexedBase.__new__(cls, label, shape=shape)
kw_args['dtype'] = dtype
kw_args['precision'] = prec
kw_args['order'] = order
kw_args['rank'] = rank
obj._kw_args = kw_args
return obj
def __getitem__(self, *args):
if self.shape and len(self.shape) != len(args):
raise IndexException('Rank mismatch.')
assumptions = {}
obj = IndexedElement(self, *args)
return obj
@property
def dtype(self):
return self.kw_args['dtype']
@property
def precision(self):
return self.kw_args['precision']
@property
def order(self):
return self.kw_args['order']
@property
def rank(self):
return self.kw_args['rank']
@property
def kw_args(self):
return self._kw_args
@property
def name(self):
return self._args[0]
[docs] def clone(self, name):
cls = eval(self.__class__.__name__)
# TODO what about kw_args in __new__?
return cls(name, shape=self.shape, dtype=self.dtype,
prec=self.precision, order=self.order, rank=self.rank)
def _eval_subs(self, old, new):
return self
[docs]class IndexedElement(Indexed):
"""
Represents a mathematical object with indices.
Examples
>>> from sympy import symbols, Idx
>>> from pyccel.ast.core import IndexedVariable
>>> i, j = symbols('i j', cls=Idx)
>>> IndexedElement('A', i, j)
A[i, j]
It is recommended that ``IndexedElement`` objects be created via ``IndexedVariable``:
>>> from pyccel.ast.core import IndexedElement
>>> A = IndexedVariable('A')
>>> IndexedElement('A', i, j) == A[i, j]
False
**todo:** fix bug. the last result must be : True
"""
def __new__(
cls,
base,
*args,
**kw_args
):
from sympy.utilities.misc import filldedent
from sympy.tensor.array.ndim_array import NDimArray
from sympy.matrices.matrices import MatrixBase
if not args:
raise IndexException('Indexed needs at least one index.')
if isinstance(base, (string_types, Symbol)):
base = IndexedBase(base)
elif not hasattr(base, '__getitem__') and not isinstance(base,
IndexedBase):
raise TypeError(filldedent("""
Indexed expects string, Symbol, or IndexedBase as base."""))
args_ = []
for arg in args:
args_.append(sympify(arg, locals=local_sympify))
args = args_
if isinstance(base, (NDimArray, collections.Iterable, Tuple,
MatrixBase)) and all([i.is_number for i in args]):
if len(args) == 1:
return base[args[0]]
else:
return base[args]
obj = Expr.__new__(cls, base, *args, **kw_args)
alloweddtypes = (NativeBool, NativeRange, NativeString)
dtype = obj.base.dtype
assumptions = {}
if isinstance(dtype, NativeInteger):
assumptions['integer'] = True
elif isinstance(dtype, NativeReal):
assumptions['real'] = True
elif isinstance(dtype, NativeComplex):
assumptions['complex'] = True
elif not isinstance(dtype, alloweddtypes):
raise TypeError('Undefined datatype')
ass_copy = assumptions.copy()
obj._assumptions = StdFactKB(assumptions)
obj._assumptions._generator = ass_copy
return obj
@property
def rank(self):
"""
Returns the rank of the ``IndexedElement`` object.
Examples
>>> from sympy import Indexed, Idx, symbols
>>> i, j, k, l, m = symbols('i:m', cls=Idx)
>>> Indexed('A', i, j).rank
2
>>> q = Indexed('A', i, j, k, l, m)
>>> q.rank
5
>>> q.rank == len(q.indices)
True
"""
n = 0
for a in self.args[1:]:
if not isinstance(a, Slice):
n += 1
return n
@property
def dtype(self):
return self.base.dtype
@property
def precision(self):
return self.base.precision
@property
def order(self):
return self.base.order
def _eval_subs(self, old, new):
return self
[docs]class String(Basic):
"""Represents the String"""
def __new__(cls, arg):
if not isinstance(arg, str):
raise TypeError('arg must be of type str')
return Basic.__new__(cls, arg)
@property
def arg(self):
return self._args[0]
def __str__(self):
return self.arg
[docs]class Concatinate(Basic):
"""Represents the String concatination operation.
left : Symbol or string or List
right : Symbol or string or List
Examples
>>> from sympy import symbols
>>> from pyccel.ast.core import Concatinate
>>> x = symbols('x')
>>> Concatinate('some_string',x)
some_string+x
>>> Concatinate('some_string','another_string')
'some_string' + 'another_string'
"""
# TODO add step
def __new__(cls, args, is_list):
args = list(args)
args = [ repr(arg) if isinstance(arg, str) else arg for arg in args]
return Basic.__new__(cls, args, is_list)
@property
def args(self):
return self._args[0]
@property
def is_list(self):
return self._args[1]
def _sympystr(self, printer):
sstr = printer.doprint
args = '+'.join(sstr(arg) for arg in self.args)
return args
[docs]class Slice(Basic):
"""Represents a slice in the code.
start : Symbol or int
starting index
end : Symbol or int
ending index
Examples
>>> from sympy import symbols
>>> from pyccel.ast.core import Slice
>>> m, n = symbols('m, n', integer=True)
>>> Slice(m,n)
m : n
>>> Slice(None,n)
: n
>>> Slice(m,None)
m :
"""
# TODO add step
# TODO check that args are integers
# TODO add negative indices
def __new__(cls, start, end):
return Basic.__new__(cls, start, end)
@property
def start(self):
return self._args[0]
@property
def end(self):
return self._args[1]
def _sympystr(self, printer):
sstr = printer.doprint
if self.start is None:
start = ''
else:
start = sstr(self.start)
if self.end is None:
end = ''
else:
end = sstr(self.end)
return '{0} : {1}'.format(start, end)
def __str__(self):
if self.start is None:
start = ''
else:
start = str(self.start)
if self.end is None:
end = ''
else:
end = str(self.end)
return '{0} : {1}'.format(start, end)
[docs]class Assert(Basic):
"""Represents a assert statement in the code.
test: Expr
boolean expression to check
Examples
"""
def __new__(cls, test):
if not isinstance(test, (bool, Relational, Boolean)):
raise TypeError('test %s is of type %s, but must be a Relational, Boolean, or a built-in bool.'
% (test, type(test)))
return Basic.__new__(cls, test)
@property
def test(self):
return self._args[0]
[docs]class Eval(Basic):
"""Basic class for eval instruction."""
pass
[docs]class Pass(Basic):
"""Basic class for pass instruction."""
pass
[docs]class Exit(Basic):
"""Basic class for exists."""
pass
[docs]class ErrorExit(Exit):
"""Exist with error."""
pass
[docs]class If(Basic):
"""Represents a if statement in the code.
args :
every argument is a tuple and
is defined as (cond, expr) where expr is a valid ast element
and cond is a boolean test.
Examples
>>> from sympy import Symbol
>>> from pyccel.ast.core import Assign, If
>>> n = Symbol('n')
>>> If(((n>1), [Assign(n,n-1)]), (True, [Assign(n,n+1)]))
If(((n>1), [Assign(n,n-1)]), (True, [Assign(n,n+1)]))
"""
# TODO add step
def __new__(cls, *args):
# (Try to) sympify args first
newargs = []
for ce in args:
cond = ce[0]
if not isinstance(cond, (bool, Relational, Boolean, Is)):
raise TypeError('Cond %s is of type %s, but must be a Relational, Boolean, Is, or a built-in bool.'
% (cond, type(cond)))
if not isinstance(ce[1], (list, Tuple, tuple)):
raise TypeError('body is not iterable')
newargs.append(ce)
return Basic.__new__(cls, *newargs)
@property
def bodies(self):
b = []
for i in self._args:
b += i[1]
return b
[docs]class IfTernaryOperator(If):
"""class for the Ternery operator"""
pass
[docs]def is_simple_assign(expr):
if not isinstance(expr, Assign):
return False
assignable = [Variable, IndexedVariable, IndexedElement]
assignable += [sp_Integer, sp_Float]
assignable = tuple(assignable)
if isinstance(expr.rhs, assignable):
return True
else:
return False
# ...
# ...
[docs]def get_initial_value(expr, var):
"""Returns the first assigned value to var in the Expression expr.
expr: Expression
any AST valid expression
var: str, Variable, DottedName, list, tuple
variable name
"""
# ...
def is_None(expr):
"""Returns True if expr is None or Nil()."""
return isinstance(expr, Nil) or expr is None
# ...
# ...
if isinstance(var, str):
return get_initial_value(expr, [var])
elif isinstance(var, DottedName):
return get_initial_value(expr, [str(var)])
elif isinstance(var, Variable):
return get_initial_value(expr, [var.name])
elif not isinstance(var, (list, tuple)):
raise TypeError('Expecting var to be str, list, tuple or Variable, given {0}'.format(type(var)))
# ...
# ...
if isinstance(expr, ValuedVariable):
if expr.variable.name in var:
return expr.value
elif isinstance(expr, Variable):
# expr.cls_base if of type ClassDef
if expr.cls_base:
return get_initial_value(expr.cls_base, var)
elif isinstance(expr, Assign):
if str(expr.lhs) in var:
return expr.rhs
elif isinstance(expr, FunctionDef):
value = get_initial_value(expr.body, var)
if not is_None(value):
r = get_initial_value(expr.arguments, value)
if 'self._linear' in var:
print ('>>>> ', var, value, r)
if not r is None:
return r
return value
elif isinstance(expr, ConstructorCall):
return get_initial_value(expr.func, var)
elif isinstance(expr, (list, tuple, Tuple)):
for i in expr:
value = get_initial_value(i, var)
# here we make a difference between None and Nil,
# since the output of our function can be None
if not value is None:
return value
elif isinstance(expr, ClassDef):
methods = expr.methods_as_dict
init_method = methods['__init__']
return get_initial_value(init_method, var)
# ...
return Nil()
# ...
# ... TODO treat other statements
[docs]def get_assigned_symbols(expr):
"""Returns all assigned symbols (as sympy Symbol) in the AST.
expr: Expression
any AST valid expression
"""
if isinstance(expr, (FunctionDef, For, While)):
return get_assigned_symbols(expr.body)
elif isinstance(expr, FunctionalFor):
return get_assigned_symbols(expr.loops)
elif isinstance(expr, If):
return get_assigned_symbols(expr.bodies)
elif iterable(expr):
symbols = []
for a in expr:
symbols += get_assigned_symbols(a)
symbols = set(symbols)
symbols = list(symbols)
return symbols
elif isinstance(expr, (Assign, AugAssign)):
if expr.lhs is None:
raise TypeError('Found None lhs')
try:
var = expr.lhs
if isinstance(var, DottedVariable):
var = expr.lhs.lhs
while isinstance(var, DottedVariable):
var = var.lhs
elif isinstance(var, IndexedElement):
var = var.base
free_symbols = var.free_symbols
symbols = list(free_symbols)
except:
# TODO must raise an Exception here
# this occurs only when parsing lapack.pyh
symbols = []
# print(type(expr.lhs), expr.lhs)
# print(expr)
# raise SystemExit('ERROR')
return symbols
return []
# ...
# ... TODO: improve and make it recursive
[docs]def get_iterable_ranges(it, var_name=None):
"""Returns ranges of an iterable object."""
if isinstance(it, Variable):
if it.cls_base is None:
raise TypeError('iterable must be an iterable Variable object'
)
# ...
def _construct_arg_Range(name):
if not isinstance(name, DottedName):
raise TypeError('Expecting a DottedName, given {0}'.format(type(name)))
if not var_name:
return DottedName(it.name.name[0], name.name[1])
else:
return DottedName(var_name, name.name[1])
# ...
cls_base = it.cls_base
if isinstance(cls_base, Range):
if not isinstance(it.name, DottedName):
raise TypeError('Expecting a DottedName, given {0}'.format(type(it.name)))
args = []
for i in [cls_base.start, cls_base.stop, cls_base.step]:
if isinstance(i, (Variable, IndexedVariable)):
arg_name = _construct_arg_Range(i.name)
arg = i.clone(arg_name)
elif isinstance(i, IndexedElement):
arg_name = _construct_arg_Range(i.base.name)
base = i.base.clone(arg_name)
indices = i.indices
arg = base[indices]
else:
raise TypeError('Wrong type, given {0}'.format(type(i)))
args += [arg]
return [Range(*args)]
elif isinstance(cls_base, Tensor):
if not isinstance(it.name, DottedName):
raise TypeError('Expecting a DottedName, given {0}'.format(type(it.name)))
# ...
ranges = []
for r in cls_base.ranges:
ranges += get_iterable_ranges(r,
var_name=str(it.name.name[0]))
# ...
return ranges
params = [str(i) for i in it.cls_parameters]
elif isinstance(it, ConstructorCall):
cls_base = it.this.cls_base
# arguments[0] is 'self'
# TODO must be improved in syntax, so that a['value'] is a sympy object
args = []
kwargs = {}
for a in it.arguments[1:]:
if isinstance(a, dict):
# we add '_' tp be conform with the private variables convention
kwargs['{0}'.format(a['key'])] = a['value']
else:
args.append(a)
# TODO improve
params = args
# for k,v in kwargs:
# params.append(k)
methods = cls_base.methods_as_dict
init_method = methods['__init__']
args = init_method.arguments[1:]
args = [str(i) for i in args]
# ...
it_method = methods['__iter__']
targets = []
starts = []
for stmt in it_method.body:
if isinstance(stmt, Assign):
targets.append(stmt.lhs)
starts.append(stmt.lhs)
names = []
for i in starts:
if isinstance(i, IndexedElement):
names.append(str(i.base))
else:
names.append(str(i))
names = list(set(names))
inits = {}
for stmt in init_method.body:
if isinstance(stmt, Assign):
if str(stmt.lhs) in names:
expr = stmt.rhs
for (a_old, a_new) in zip(args, params):
dtype = datatype(stmt.rhs.dtype)
v_old = Variable(dtype, a_old)
if isinstance(a_new, (IndexedVariable,
IndexedElement, str, Variable)):
v_new = Variable(dtype, a_new)
else:
v_new = a_new
expr = subs(expr, v_old, v_new)
inits[str(stmt.lhs)] = expr
_starts = []
for i in starts:
if isinstance(i, IndexedElement):
_starts.append(i.base)
else:
_starts.append(i)
starts = [inits[str(i)] for i in _starts]
# ...
def _find_stopping_criterium(stmts):
for stmt in stmts:
if isinstance(stmt, If):
if not len(stmt.args) == 2:
raise ValueError('Wrong __next__ pattern')
(ct, et) = stmt.args[0]
(cf, ef) = stmt.args[1]
for i in et:
if isinstance(i, Raise):
return cf
for i in ef:
if isinstance(i, Raise):
return ct
raise TypeError('Wrong type for __next__ pattern')
return None
# ...
# ...
def doit(expr, targets):
if isinstance(expr, Relational):
if str(expr.lhs) in targets and expr.rel_op in ['<', '<=']:
return expr.rhs
elif str(expr.rhs) in targets and expr.rel_op in ['>', '>='
]:
return expr.lhs
else:
return None
elif isinstance(expr, And):
return [doit(a, targets) for a in expr.args]
else:
raise TypeError('Expecting And logical expression.')
# ...
# ...
next_method = methods['__next__']
ends = []
cond = _find_stopping_criterium(next_method.body)
# TODO treate case of cond with 'and' operation
# TODO we should avoid using str
# must change target from DottedName to Variable
targets = [str(i) for i in targets]
ends = doit(cond, targets)
# TODO not use str
if not isinstance(ends, (list, tuple)):
ends = [ends]
names = []
for i in ends:
if isinstance(i, IndexedElement):
names.append(str(i.base))
else:
names.append(str(i))
names = list(set(names))
inits = {}
for stmt in init_method.body:
if isinstance(stmt, Assign):
if str(stmt.lhs) in names:
expr = stmt.rhs
for (a_old, a_new) in zip(args, params):
dtype = datatype(stmt.rhs.dtype)
v_old = Variable(dtype, a_old)
if isinstance(a_new, (IndexedVariable,
IndexedElement, str, Variable)):
v_new = Variable(dtype, a_new)
else:
v_new = a_new
expr = subs(expr, v_old, v_new)
inits[str(stmt.lhs)] = expr
_ends = []
for i in ends:
if isinstance(i, IndexedElement):
_ends.append(i.base)
else:
_ends.append(i)
ends = [inits[str(i)] for i in _ends]
# ...
# ...
if not len(ends) == len(starts):
raise ValueError('wrong number of starts/ends')
# ...
return [Range(s, e, 1) for (s, e) in zip(starts, ends)]
# ...