|
"""Fortran/C symbolic expressions |
|
|
|
References: |
|
- J3/21-007: Draft Fortran 202x. https://j3-fortran.org/doc/year/21/21-007.pdf |
|
|
|
Copyright 1999 -- 2011 Pearu Peterson all rights reserved. |
|
Copyright 2011 -- present NumPy Developers. |
|
Permission to use, modify, and distribute this software is given under the |
|
terms of the NumPy License. |
|
|
|
NO WARRANTY IS EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK. |
|
""" |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
__all__ = ['Expr'] |
|
|
|
|
|
import re |
|
import warnings |
|
from enum import Enum |
|
from math import gcd |
|
|
|
|
|
class Language(Enum): |
|
""" |
|
Used as Expr.tostring language argument. |
|
""" |
|
Python = 0 |
|
Fortran = 1 |
|
C = 2 |
|
|
|
|
|
class Op(Enum): |
|
""" |
|
Used as Expr op attribute. |
|
""" |
|
INTEGER = 10 |
|
REAL = 12 |
|
COMPLEX = 15 |
|
STRING = 20 |
|
ARRAY = 30 |
|
SYMBOL = 40 |
|
TERNARY = 100 |
|
APPLY = 200 |
|
INDEXING = 210 |
|
CONCAT = 220 |
|
RELATIONAL = 300 |
|
TERMS = 1000 |
|
FACTORS = 2000 |
|
REF = 3000 |
|
DEREF = 3001 |
|
|
|
|
|
class RelOp(Enum): |
|
""" |
|
Used in Op.RELATIONAL expression to specify the function part. |
|
""" |
|
EQ = 1 |
|
NE = 2 |
|
LT = 3 |
|
LE = 4 |
|
GT = 5 |
|
GE = 6 |
|
|
|
@classmethod |
|
def fromstring(cls, s, language=Language.C): |
|
if language is Language.Fortran: |
|
return {'.eq.': RelOp.EQ, '.ne.': RelOp.NE, |
|
'.lt.': RelOp.LT, '.le.': RelOp.LE, |
|
'.gt.': RelOp.GT, '.ge.': RelOp.GE}[s.lower()] |
|
return {'==': RelOp.EQ, '!=': RelOp.NE, '<': RelOp.LT, |
|
'<=': RelOp.LE, '>': RelOp.GT, '>=': RelOp.GE}[s] |
|
|
|
def tostring(self, language=Language.C): |
|
if language is Language.Fortran: |
|
return {RelOp.EQ: '.eq.', RelOp.NE: '.ne.', |
|
RelOp.LT: '.lt.', RelOp.LE: '.le.', |
|
RelOp.GT: '.gt.', RelOp.GE: '.ge.'}[self] |
|
return {RelOp.EQ: '==', RelOp.NE: '!=', |
|
RelOp.LT: '<', RelOp.LE: '<=', |
|
RelOp.GT: '>', RelOp.GE: '>='}[self] |
|
|
|
|
|
class ArithOp(Enum): |
|
""" |
|
Used in Op.APPLY expression to specify the function part. |
|
""" |
|
POS = 1 |
|
NEG = 2 |
|
ADD = 3 |
|
SUB = 4 |
|
MUL = 5 |
|
DIV = 6 |
|
POW = 7 |
|
|
|
|
|
class OpError(Exception): |
|
pass |
|
|
|
|
|
class Precedence(Enum): |
|
""" |
|
Used as Expr.tostring precedence argument. |
|
""" |
|
ATOM = 0 |
|
POWER = 1 |
|
UNARY = 2 |
|
PRODUCT = 3 |
|
SUM = 4 |
|
LT = 6 |
|
EQ = 7 |
|
LAND = 11 |
|
LOR = 12 |
|
TERNARY = 13 |
|
ASSIGN = 14 |
|
TUPLE = 15 |
|
NONE = 100 |
|
|
|
|
|
integer_types = (int,) |
|
number_types = (int, float) |
|
|
|
|
|
def _pairs_add(d, k, v): |
|
|
|
c = d.get(k) |
|
if c is None: |
|
d[k] = v |
|
else: |
|
c = c + v |
|
if c: |
|
d[k] = c |
|
else: |
|
del d[k] |
|
|
|
|
|
class ExprWarning(UserWarning): |
|
pass |
|
|
|
|
|
def ewarn(message): |
|
warnings.warn(message, ExprWarning, stacklevel=2) |
|
|
|
|
|
class Expr: |
|
"""Represents a Fortran expression as a op-data pair. |
|
|
|
Expr instances are hashable and sortable. |
|
""" |
|
|
|
@staticmethod |
|
def parse(s, language=Language.C): |
|
"""Parse a Fortran expression to a Expr. |
|
""" |
|
return fromstring(s, language=language) |
|
|
|
def __init__(self, op, data): |
|
assert isinstance(op, Op) |
|
|
|
|
|
if op is Op.INTEGER: |
|
|
|
|
|
assert isinstance(data, tuple) and len(data) == 2 |
|
assert isinstance(data[0], int) |
|
assert isinstance(data[1], (int, str)), data |
|
elif op is Op.REAL: |
|
|
|
|
|
assert isinstance(data, tuple) and len(data) == 2 |
|
assert isinstance(data[0], float) |
|
assert isinstance(data[1], (int, str)), data |
|
elif op is Op.COMPLEX: |
|
|
|
assert isinstance(data, tuple) and len(data) == 2 |
|
elif op is Op.STRING: |
|
|
|
|
|
assert isinstance(data, tuple) and len(data) == 2 |
|
assert (isinstance(data[0], str) |
|
and data[0][::len(data[0])-1] in ('""', "''", '@@')) |
|
assert isinstance(data[1], (int, str)), data |
|
elif op is Op.SYMBOL: |
|
|
|
assert hash(data) is not None |
|
elif op in (Op.ARRAY, Op.CONCAT): |
|
|
|
assert isinstance(data, tuple) |
|
assert all(isinstance(item, Expr) for item in data), data |
|
elif op in (Op.TERMS, Op.FACTORS): |
|
|
|
|
|
assert isinstance(data, dict) |
|
elif op is Op.APPLY: |
|
|
|
|
|
assert isinstance(data, tuple) and len(data) == 3 |
|
|
|
assert hash(data[0]) is not None |
|
assert isinstance(data[1], tuple) |
|
assert isinstance(data[2], dict) |
|
elif op is Op.INDEXING: |
|
|
|
assert isinstance(data, tuple) and len(data) == 2 |
|
|
|
assert hash(data[0]) is not None |
|
elif op is Op.TERNARY: |
|
|
|
assert isinstance(data, tuple) and len(data) == 3 |
|
elif op in (Op.REF, Op.DEREF): |
|
|
|
assert isinstance(data, Expr) |
|
elif op is Op.RELATIONAL: |
|
|
|
assert isinstance(data, tuple) and len(data) == 3 |
|
else: |
|
raise NotImplementedError( |
|
f'unknown op or missing sanity check: {op}') |
|
|
|
self.op = op |
|
self.data = data |
|
|
|
def __eq__(self, other): |
|
return (isinstance(other, Expr) |
|
and self.op is other.op |
|
and self.data == other.data) |
|
|
|
def __hash__(self): |
|
if self.op in (Op.TERMS, Op.FACTORS): |
|
data = tuple(sorted(self.data.items())) |
|
elif self.op is Op.APPLY: |
|
data = self.data[:2] + tuple(sorted(self.data[2].items())) |
|
else: |
|
data = self.data |
|
return hash((self.op, data)) |
|
|
|
def __lt__(self, other): |
|
if isinstance(other, Expr): |
|
if self.op is not other.op: |
|
return self.op.value < other.op.value |
|
if self.op in (Op.TERMS, Op.FACTORS): |
|
return (tuple(sorted(self.data.items())) |
|
< tuple(sorted(other.data.items()))) |
|
if self.op is Op.APPLY: |
|
if self.data[:2] != other.data[:2]: |
|
return self.data[:2] < other.data[:2] |
|
return tuple(sorted(self.data[2].items())) < tuple( |
|
sorted(other.data[2].items())) |
|
return self.data < other.data |
|
return NotImplemented |
|
|
|
def __le__(self, other): return self == other or self < other |
|
|
|
def __gt__(self, other): return not (self <= other) |
|
|
|
def __ge__(self, other): return not (self < other) |
|
|
|
def __repr__(self): |
|
return f'{type(self).__name__}({self.op}, {self.data!r})' |
|
|
|
def __str__(self): |
|
return self.tostring() |
|
|
|
def tostring(self, parent_precedence=Precedence.NONE, |
|
language=Language.Fortran): |
|
"""Return a string representation of Expr. |
|
""" |
|
if self.op in (Op.INTEGER, Op.REAL): |
|
precedence = (Precedence.SUM if self.data[0] < 0 |
|
else Precedence.ATOM) |
|
r = str(self.data[0]) + (f'_{self.data[1]}' |
|
if self.data[1] != 4 else '') |
|
elif self.op is Op.COMPLEX: |
|
r = ', '.join(item.tostring(Precedence.TUPLE, language=language) |
|
for item in self.data) |
|
r = '(' + r + ')' |
|
precedence = Precedence.ATOM |
|
elif self.op is Op.SYMBOL: |
|
precedence = Precedence.ATOM |
|
r = str(self.data) |
|
elif self.op is Op.STRING: |
|
r = self.data[0] |
|
if self.data[1] != 1: |
|
r = self.data[1] + '_' + r |
|
precedence = Precedence.ATOM |
|
elif self.op is Op.ARRAY: |
|
r = ', '.join(item.tostring(Precedence.TUPLE, language=language) |
|
for item in self.data) |
|
r = '[' + r + ']' |
|
precedence = Precedence.ATOM |
|
elif self.op is Op.TERMS: |
|
terms = [] |
|
for term, coeff in sorted(self.data.items()): |
|
if coeff < 0: |
|
op = ' - ' |
|
coeff = -coeff |
|
else: |
|
op = ' + ' |
|
if coeff == 1: |
|
term = term.tostring(Precedence.SUM, language=language) |
|
else: |
|
if term == as_number(1): |
|
term = str(coeff) |
|
else: |
|
term = f'{coeff} * ' + term.tostring( |
|
Precedence.PRODUCT, language=language) |
|
if terms: |
|
terms.append(op) |
|
elif op == ' - ': |
|
terms.append('-') |
|
terms.append(term) |
|
r = ''.join(terms) or '0' |
|
precedence = Precedence.SUM if terms else Precedence.ATOM |
|
elif self.op is Op.FACTORS: |
|
factors = [] |
|
tail = [] |
|
for base, exp in sorted(self.data.items()): |
|
op = ' * ' |
|
if exp == 1: |
|
factor = base.tostring(Precedence.PRODUCT, |
|
language=language) |
|
elif language is Language.C: |
|
if exp in range(2, 10): |
|
factor = base.tostring(Precedence.PRODUCT, |
|
language=language) |
|
factor = ' * '.join([factor] * exp) |
|
elif exp in range(-10, 0): |
|
factor = base.tostring(Precedence.PRODUCT, |
|
language=language) |
|
tail += [factor] * -exp |
|
continue |
|
else: |
|
factor = base.tostring(Precedence.TUPLE, |
|
language=language) |
|
factor = f'pow({factor}, {exp})' |
|
else: |
|
factor = base.tostring(Precedence.POWER, |
|
language=language) + f' ** {exp}' |
|
if factors: |
|
factors.append(op) |
|
factors.append(factor) |
|
if tail: |
|
if not factors: |
|
factors += ['1'] |
|
factors += ['/', '(', ' * '.join(tail), ')'] |
|
r = ''.join(factors) or '1' |
|
precedence = Precedence.PRODUCT if factors else Precedence.ATOM |
|
elif self.op is Op.APPLY: |
|
name, args, kwargs = self.data |
|
if name is ArithOp.DIV and language is Language.C: |
|
numer, denom = [arg.tostring(Precedence.PRODUCT, |
|
language=language) |
|
for arg in args] |
|
r = f'{numer} / {denom}' |
|
precedence = Precedence.PRODUCT |
|
else: |
|
args = [arg.tostring(Precedence.TUPLE, language=language) |
|
for arg in args] |
|
args += [k + '=' + v.tostring(Precedence.NONE) |
|
for k, v in kwargs.items()] |
|
r = f'{name}({", ".join(args)})' |
|
precedence = Precedence.ATOM |
|
elif self.op is Op.INDEXING: |
|
name = self.data[0] |
|
args = [arg.tostring(Precedence.TUPLE, language=language) |
|
for arg in self.data[1:]] |
|
r = f'{name}[{", ".join(args)}]' |
|
precedence = Precedence.ATOM |
|
elif self.op is Op.CONCAT: |
|
args = [arg.tostring(Precedence.PRODUCT, language=language) |
|
for arg in self.data] |
|
r = " // ".join(args) |
|
precedence = Precedence.PRODUCT |
|
elif self.op is Op.TERNARY: |
|
cond, expr1, expr2 = [a.tostring(Precedence.TUPLE, |
|
language=language) |
|
for a in self.data] |
|
if language is Language.C: |
|
r = f'({cond}?{expr1}:{expr2})' |
|
elif language is Language.Python: |
|
r = f'({expr1} if {cond} else {expr2})' |
|
elif language is Language.Fortran: |
|
r = f'merge({expr1}, {expr2}, {cond})' |
|
else: |
|
raise NotImplementedError( |
|
f'tostring for {self.op} and {language}') |
|
precedence = Precedence.ATOM |
|
elif self.op is Op.REF: |
|
r = '&' + self.data.tostring(Precedence.UNARY, language=language) |
|
precedence = Precedence.UNARY |
|
elif self.op is Op.DEREF: |
|
r = '*' + self.data.tostring(Precedence.UNARY, language=language) |
|
precedence = Precedence.UNARY |
|
elif self.op is Op.RELATIONAL: |
|
rop, left, right = self.data |
|
precedence = (Precedence.EQ if rop in (RelOp.EQ, RelOp.NE) |
|
else Precedence.LT) |
|
left = left.tostring(precedence, language=language) |
|
right = right.tostring(precedence, language=language) |
|
rop = rop.tostring(language=language) |
|
r = f'{left} {rop} {right}' |
|
else: |
|
raise NotImplementedError(f'tostring for op {self.op}') |
|
if parent_precedence.value < precedence.value: |
|
|
|
|
|
return '(' + r + ')' |
|
return r |
|
|
|
def __pos__(self): |
|
return self |
|
|
|
def __neg__(self): |
|
return self * -1 |
|
|
|
def __add__(self, other): |
|
other = as_expr(other) |
|
if isinstance(other, Expr): |
|
if self.op is other.op: |
|
if self.op in (Op.INTEGER, Op.REAL): |
|
return as_number( |
|
self.data[0] + other.data[0], |
|
max(self.data[1], other.data[1])) |
|
if self.op is Op.COMPLEX: |
|
r1, i1 = self.data |
|
r2, i2 = other.data |
|
return as_complex(r1 + r2, i1 + i2) |
|
if self.op is Op.TERMS: |
|
r = Expr(self.op, dict(self.data)) |
|
for k, v in other.data.items(): |
|
_pairs_add(r.data, k, v) |
|
return normalize(r) |
|
if self.op is Op.COMPLEX and other.op in (Op.INTEGER, Op.REAL): |
|
return self + as_complex(other) |
|
elif self.op in (Op.INTEGER, Op.REAL) and other.op is Op.COMPLEX: |
|
return as_complex(self) + other |
|
elif self.op is Op.REAL and other.op is Op.INTEGER: |
|
return self + as_real(other, kind=self.data[1]) |
|
elif self.op is Op.INTEGER and other.op is Op.REAL: |
|
return as_real(self, kind=other.data[1]) + other |
|
return as_terms(self) + as_terms(other) |
|
return NotImplemented |
|
|
|
def __radd__(self, other): |
|
if isinstance(other, number_types): |
|
return as_number(other) + self |
|
return NotImplemented |
|
|
|
def __sub__(self, other): |
|
return self + (-other) |
|
|
|
def __rsub__(self, other): |
|
if isinstance(other, number_types): |
|
return as_number(other) - self |
|
return NotImplemented |
|
|
|
def __mul__(self, other): |
|
other = as_expr(other) |
|
if isinstance(other, Expr): |
|
if self.op is other.op: |
|
if self.op in (Op.INTEGER, Op.REAL): |
|
return as_number(self.data[0] * other.data[0], |
|
max(self.data[1], other.data[1])) |
|
elif self.op is Op.COMPLEX: |
|
r1, i1 = self.data |
|
r2, i2 = other.data |
|
return as_complex(r1 * r2 - i1 * i2, r1 * i2 + r2 * i1) |
|
|
|
if self.op is Op.FACTORS: |
|
r = Expr(self.op, dict(self.data)) |
|
for k, v in other.data.items(): |
|
_pairs_add(r.data, k, v) |
|
return normalize(r) |
|
elif self.op is Op.TERMS: |
|
r = Expr(self.op, {}) |
|
for t1, c1 in self.data.items(): |
|
for t2, c2 in other.data.items(): |
|
_pairs_add(r.data, t1 * t2, c1 * c2) |
|
return normalize(r) |
|
|
|
if self.op is Op.COMPLEX and other.op in (Op.INTEGER, Op.REAL): |
|
return self * as_complex(other) |
|
elif other.op is Op.COMPLEX and self.op in (Op.INTEGER, Op.REAL): |
|
return as_complex(self) * other |
|
elif self.op is Op.REAL and other.op is Op.INTEGER: |
|
return self * as_real(other, kind=self.data[1]) |
|
elif self.op is Op.INTEGER and other.op is Op.REAL: |
|
return as_real(self, kind=other.data[1]) * other |
|
|
|
if self.op is Op.TERMS: |
|
return self * as_terms(other) |
|
elif other.op is Op.TERMS: |
|
return as_terms(self) * other |
|
|
|
return as_factors(self) * as_factors(other) |
|
return NotImplemented |
|
|
|
def __rmul__(self, other): |
|
if isinstance(other, number_types): |
|
return as_number(other) * self |
|
return NotImplemented |
|
|
|
def __pow__(self, other): |
|
other = as_expr(other) |
|
if isinstance(other, Expr): |
|
if other.op is Op.INTEGER: |
|
exponent = other.data[0] |
|
|
|
if exponent == 0: |
|
return as_number(1) |
|
if exponent == 1: |
|
return self |
|
if exponent > 0: |
|
if self.op is Op.FACTORS: |
|
r = Expr(self.op, {}) |
|
for k, v in self.data.items(): |
|
r.data[k] = v * exponent |
|
return normalize(r) |
|
return self * (self ** (exponent - 1)) |
|
elif exponent != -1: |
|
return (self ** (-exponent)) ** -1 |
|
return Expr(Op.FACTORS, {self: exponent}) |
|
return as_apply(ArithOp.POW, self, other) |
|
return NotImplemented |
|
|
|
def __truediv__(self, other): |
|
other = as_expr(other) |
|
if isinstance(other, Expr): |
|
|
|
|
|
return normalize(as_apply(ArithOp.DIV, self, other)) |
|
return NotImplemented |
|
|
|
def __rtruediv__(self, other): |
|
other = as_expr(other) |
|
if isinstance(other, Expr): |
|
return other / self |
|
return NotImplemented |
|
|
|
def __floordiv__(self, other): |
|
other = as_expr(other) |
|
if isinstance(other, Expr): |
|
|
|
|
|
return normalize(Expr(Op.CONCAT, (self, other))) |
|
return NotImplemented |
|
|
|
def __rfloordiv__(self, other): |
|
other = as_expr(other) |
|
if isinstance(other, Expr): |
|
return other // self |
|
return NotImplemented |
|
|
|
def __call__(self, *args, **kwargs): |
|
|
|
|
|
|
|
|
|
|
|
return as_apply(self, *map(as_expr, args), |
|
**dict((k, as_expr(v)) for k, v in kwargs.items())) |
|
|
|
def __getitem__(self, index): |
|
|
|
|
|
index = as_expr(index) |
|
if not isinstance(index, tuple): |
|
index = index, |
|
if len(index) > 1: |
|
ewarn(f'C-index should be a single expression but got `{index}`') |
|
return Expr(Op.INDEXING, (self,) + index) |
|
|
|
def substitute(self, symbols_map): |
|
"""Recursively substitute symbols with values in symbols map. |
|
|
|
Symbols map is a dictionary of symbol-expression pairs. |
|
""" |
|
if self.op is Op.SYMBOL: |
|
value = symbols_map.get(self) |
|
if value is None: |
|
return self |
|
m = re.match(r'\A(@__f2py_PARENTHESIS_(\w+)_\d+@)\Z', self.data) |
|
if m: |
|
|
|
items, paren = m.groups() |
|
if paren in ['ROUNDDIV', 'SQUARE']: |
|
return as_array(value) |
|
assert paren == 'ROUND', (paren, value) |
|
return value |
|
if self.op in (Op.INTEGER, Op.REAL, Op.STRING): |
|
return self |
|
if self.op in (Op.ARRAY, Op.COMPLEX): |
|
return Expr(self.op, tuple(item.substitute(symbols_map) |
|
for item in self.data)) |
|
if self.op is Op.CONCAT: |
|
return normalize(Expr(self.op, tuple(item.substitute(symbols_map) |
|
for item in self.data))) |
|
if self.op is Op.TERMS: |
|
r = None |
|
for term, coeff in self.data.items(): |
|
if r is None: |
|
r = term.substitute(symbols_map) * coeff |
|
else: |
|
r += term.substitute(symbols_map) * coeff |
|
if r is None: |
|
ewarn('substitute: empty TERMS expression interpreted as' |
|
' int-literal 0') |
|
return as_number(0) |
|
return r |
|
if self.op is Op.FACTORS: |
|
r = None |
|
for base, exponent in self.data.items(): |
|
if r is None: |
|
r = base.substitute(symbols_map) ** exponent |
|
else: |
|
r *= base.substitute(symbols_map) ** exponent |
|
if r is None: |
|
ewarn('substitute: empty FACTORS expression interpreted' |
|
' as int-literal 1') |
|
return as_number(1) |
|
return r |
|
if self.op is Op.APPLY: |
|
target, args, kwargs = self.data |
|
if isinstance(target, Expr): |
|
target = target.substitute(symbols_map) |
|
args = tuple(a.substitute(symbols_map) for a in args) |
|
kwargs = dict((k, v.substitute(symbols_map)) |
|
for k, v in kwargs.items()) |
|
return normalize(Expr(self.op, (target, args, kwargs))) |
|
if self.op is Op.INDEXING: |
|
func = self.data[0] |
|
if isinstance(func, Expr): |
|
func = func.substitute(symbols_map) |
|
args = tuple(a.substitute(symbols_map) for a in self.data[1:]) |
|
return normalize(Expr(self.op, (func,) + args)) |
|
if self.op is Op.TERNARY: |
|
operands = tuple(a.substitute(symbols_map) for a in self.data) |
|
return normalize(Expr(self.op, operands)) |
|
if self.op in (Op.REF, Op.DEREF): |
|
return normalize(Expr(self.op, self.data.substitute(symbols_map))) |
|
if self.op is Op.RELATIONAL: |
|
rop, left, right = self.data |
|
left = left.substitute(symbols_map) |
|
right = right.substitute(symbols_map) |
|
return normalize(Expr(self.op, (rop, left, right))) |
|
raise NotImplementedError(f'substitute method for {self.op}: {self!r}') |
|
|
|
def traverse(self, visit, *args, **kwargs): |
|
"""Traverse expression tree with visit function. |
|
|
|
The visit function is applied to an expression with given args |
|
and kwargs. |
|
|
|
Traverse call returns an expression returned by visit when not |
|
None, otherwise return a new normalized expression with |
|
traverse-visit sub-expressions. |
|
""" |
|
result = visit(self, *args, **kwargs) |
|
if result is not None: |
|
return result |
|
|
|
if self.op in (Op.INTEGER, Op.REAL, Op.STRING, Op.SYMBOL): |
|
return self |
|
elif self.op in (Op.COMPLEX, Op.ARRAY, Op.CONCAT, Op.TERNARY): |
|
return normalize(Expr(self.op, tuple( |
|
item.traverse(visit, *args, **kwargs) |
|
for item in self.data))) |
|
elif self.op in (Op.TERMS, Op.FACTORS): |
|
data = {} |
|
for k, v in self.data.items(): |
|
k = k.traverse(visit, *args, **kwargs) |
|
v = (v.traverse(visit, *args, **kwargs) |
|
if isinstance(v, Expr) else v) |
|
if k in data: |
|
v = data[k] + v |
|
data[k] = v |
|
return normalize(Expr(self.op, data)) |
|
elif self.op is Op.APPLY: |
|
obj = self.data[0] |
|
func = (obj.traverse(visit, *args, **kwargs) |
|
if isinstance(obj, Expr) else obj) |
|
operands = tuple(operand.traverse(visit, *args, **kwargs) |
|
for operand in self.data[1]) |
|
kwoperands = dict((k, v.traverse(visit, *args, **kwargs)) |
|
for k, v in self.data[2].items()) |
|
return normalize(Expr(self.op, (func, operands, kwoperands))) |
|
elif self.op is Op.INDEXING: |
|
obj = self.data[0] |
|
obj = (obj.traverse(visit, *args, **kwargs) |
|
if isinstance(obj, Expr) else obj) |
|
indices = tuple(index.traverse(visit, *args, **kwargs) |
|
for index in self.data[1:]) |
|
return normalize(Expr(self.op, (obj,) + indices)) |
|
elif self.op in (Op.REF, Op.DEREF): |
|
return normalize(Expr(self.op, |
|
self.data.traverse(visit, *args, **kwargs))) |
|
elif self.op is Op.RELATIONAL: |
|
rop, left, right = self.data |
|
left = left.traverse(visit, *args, **kwargs) |
|
right = right.traverse(visit, *args, **kwargs) |
|
return normalize(Expr(self.op, (rop, left, right))) |
|
raise NotImplementedError(f'traverse method for {self.op}') |
|
|
|
def contains(self, other): |
|
"""Check if self contains other. |
|
""" |
|
found = [] |
|
|
|
def visit(expr, found=found): |
|
if found: |
|
return expr |
|
elif expr == other: |
|
found.append(1) |
|
return expr |
|
|
|
self.traverse(visit) |
|
|
|
return len(found) != 0 |
|
|
|
def symbols(self): |
|
"""Return a set of symbols contained in self. |
|
""" |
|
found = set() |
|
|
|
def visit(expr, found=found): |
|
if expr.op is Op.SYMBOL: |
|
found.add(expr) |
|
|
|
self.traverse(visit) |
|
|
|
return found |
|
|
|
def polynomial_atoms(self): |
|
"""Return a set of expressions used as atoms in polynomial self. |
|
""" |
|
found = set() |
|
|
|
def visit(expr, found=found): |
|
if expr.op is Op.FACTORS: |
|
for b in expr.data: |
|
b.traverse(visit) |
|
return expr |
|
if expr.op in (Op.TERMS, Op.COMPLEX): |
|
return |
|
if expr.op is Op.APPLY and isinstance(expr.data[0], ArithOp): |
|
if expr.data[0] is ArithOp.POW: |
|
expr.data[1][0].traverse(visit) |
|
return expr |
|
return |
|
if expr.op in (Op.INTEGER, Op.REAL): |
|
return expr |
|
|
|
found.add(expr) |
|
|
|
if expr.op in (Op.INDEXING, Op.APPLY): |
|
return expr |
|
|
|
self.traverse(visit) |
|
|
|
return found |
|
|
|
def linear_solve(self, symbol): |
|
"""Return a, b such that a * symbol + b == self. |
|
|
|
If self is not linear with respect to symbol, raise RuntimeError. |
|
""" |
|
b = self.substitute({symbol: as_number(0)}) |
|
ax = self - b |
|
a = ax.substitute({symbol: as_number(1)}) |
|
|
|
zero, _ = as_numer_denom(a * symbol - ax) |
|
|
|
if zero != as_number(0): |
|
raise RuntimeError(f'not a {symbol}-linear equation:' |
|
f' {a} * {symbol} + {b} == {self}') |
|
return a, b |
|
|
|
|
|
def normalize(obj): |
|
"""Normalize Expr and apply basic evaluation methods. |
|
""" |
|
if not isinstance(obj, Expr): |
|
return obj |
|
|
|
if obj.op is Op.TERMS: |
|
d = {} |
|
for t, c in obj.data.items(): |
|
if c == 0: |
|
continue |
|
if t.op is Op.COMPLEX and c != 1: |
|
t = t * c |
|
c = 1 |
|
if t.op is Op.TERMS: |
|
for t1, c1 in t.data.items(): |
|
_pairs_add(d, t1, c1 * c) |
|
else: |
|
_pairs_add(d, t, c) |
|
if len(d) == 0: |
|
|
|
return as_number(0) |
|
elif len(d) == 1: |
|
(t, c), = d.items() |
|
if c == 1: |
|
return t |
|
return Expr(Op.TERMS, d) |
|
|
|
if obj.op is Op.FACTORS: |
|
coeff = 1 |
|
d = {} |
|
for b, e in obj.data.items(): |
|
if e == 0: |
|
continue |
|
if b.op is Op.TERMS and isinstance(e, integer_types) and e > 1: |
|
|
|
b = b * (b ** (e - 1)) |
|
e = 1 |
|
|
|
if b.op in (Op.INTEGER, Op.REAL): |
|
if e == 1: |
|
coeff *= b.data[0] |
|
elif e > 0: |
|
coeff *= b.data[0] ** e |
|
else: |
|
_pairs_add(d, b, e) |
|
elif b.op is Op.FACTORS: |
|
if e > 0 and isinstance(e, integer_types): |
|
for b1, e1 in b.data.items(): |
|
_pairs_add(d, b1, e1 * e) |
|
else: |
|
_pairs_add(d, b, e) |
|
else: |
|
_pairs_add(d, b, e) |
|
if len(d) == 0 or coeff == 0: |
|
|
|
assert isinstance(coeff, number_types) |
|
return as_number(coeff) |
|
elif len(d) == 1: |
|
(b, e), = d.items() |
|
if e == 1: |
|
t = b |
|
else: |
|
t = Expr(Op.FACTORS, d) |
|
if coeff == 1: |
|
return t |
|
return Expr(Op.TERMS, {t: coeff}) |
|
elif coeff == 1: |
|
return Expr(Op.FACTORS, d) |
|
else: |
|
return Expr(Op.TERMS, {Expr(Op.FACTORS, d): coeff}) |
|
|
|
if obj.op is Op.APPLY and obj.data[0] is ArithOp.DIV: |
|
dividend, divisor = obj.data[1] |
|
t1, c1 = as_term_coeff(dividend) |
|
t2, c2 = as_term_coeff(divisor) |
|
if isinstance(c1, integer_types) and isinstance(c2, integer_types): |
|
g = gcd(c1, c2) |
|
c1, c2 = c1//g, c2//g |
|
else: |
|
c1, c2 = c1/c2, 1 |
|
|
|
if t1.op is Op.APPLY and t1.data[0] is ArithOp.DIV: |
|
numer = t1.data[1][0] * c1 |
|
denom = t1.data[1][1] * t2 * c2 |
|
return as_apply(ArithOp.DIV, numer, denom) |
|
|
|
if t2.op is Op.APPLY and t2.data[0] is ArithOp.DIV: |
|
numer = t2.data[1][1] * t1 * c1 |
|
denom = t2.data[1][0] * c2 |
|
return as_apply(ArithOp.DIV, numer, denom) |
|
|
|
d = dict(as_factors(t1).data) |
|
for b, e in as_factors(t2).data.items(): |
|
_pairs_add(d, b, -e) |
|
numer, denom = {}, {} |
|
for b, e in d.items(): |
|
if e > 0: |
|
numer[b] = e |
|
else: |
|
denom[b] = -e |
|
numer = normalize(Expr(Op.FACTORS, numer)) * c1 |
|
denom = normalize(Expr(Op.FACTORS, denom)) * c2 |
|
|
|
if denom.op in (Op.INTEGER, Op.REAL) and denom.data[0] == 1: |
|
|
|
return numer |
|
return as_apply(ArithOp.DIV, numer, denom) |
|
|
|
if obj.op is Op.CONCAT: |
|
lst = [obj.data[0]] |
|
for s in obj.data[1:]: |
|
last = lst[-1] |
|
if ( |
|
last.op is Op.STRING |
|
and s.op is Op.STRING |
|
and last.data[0][0] in '"\'' |
|
and s.data[0][0] == last.data[0][-1] |
|
): |
|
new_last = as_string(last.data[0][:-1] + s.data[0][1:], |
|
max(last.data[1], s.data[1])) |
|
lst[-1] = new_last |
|
else: |
|
lst.append(s) |
|
if len(lst) == 1: |
|
return lst[0] |
|
return Expr(Op.CONCAT, tuple(lst)) |
|
|
|
if obj.op is Op.TERNARY: |
|
cond, expr1, expr2 = map(normalize, obj.data) |
|
if cond.op is Op.INTEGER: |
|
return expr1 if cond.data[0] else expr2 |
|
return Expr(Op.TERNARY, (cond, expr1, expr2)) |
|
|
|
return obj |
|
|
|
|
|
def as_expr(obj): |
|
"""Convert non-Expr objects to Expr objects. |
|
""" |
|
if isinstance(obj, complex): |
|
return as_complex(obj.real, obj.imag) |
|
if isinstance(obj, number_types): |
|
return as_number(obj) |
|
if isinstance(obj, str): |
|
|
|
|
|
return as_string(repr(obj)) |
|
if isinstance(obj, tuple): |
|
return tuple(map(as_expr, obj)) |
|
return obj |
|
|
|
|
|
def as_symbol(obj): |
|
"""Return object as SYMBOL expression (variable or unparsed expression). |
|
""" |
|
return Expr(Op.SYMBOL, obj) |
|
|
|
|
|
def as_number(obj, kind=4): |
|
"""Return object as INTEGER or REAL constant. |
|
""" |
|
if isinstance(obj, int): |
|
return Expr(Op.INTEGER, (obj, kind)) |
|
if isinstance(obj, float): |
|
return Expr(Op.REAL, (obj, kind)) |
|
if isinstance(obj, Expr): |
|
if obj.op in (Op.INTEGER, Op.REAL): |
|
return obj |
|
raise OpError(f'cannot convert {obj} to INTEGER or REAL constant') |
|
|
|
|
|
def as_integer(obj, kind=4): |
|
"""Return object as INTEGER constant. |
|
""" |
|
if isinstance(obj, int): |
|
return Expr(Op.INTEGER, (obj, kind)) |
|
if isinstance(obj, Expr): |
|
if obj.op is Op.INTEGER: |
|
return obj |
|
raise OpError(f'cannot convert {obj} to INTEGER constant') |
|
|
|
|
|
def as_real(obj, kind=4): |
|
"""Return object as REAL constant. |
|
""" |
|
if isinstance(obj, int): |
|
return Expr(Op.REAL, (float(obj), kind)) |
|
if isinstance(obj, float): |
|
return Expr(Op.REAL, (obj, kind)) |
|
if isinstance(obj, Expr): |
|
if obj.op is Op.REAL: |
|
return obj |
|
elif obj.op is Op.INTEGER: |
|
return Expr(Op.REAL, (float(obj.data[0]), kind)) |
|
raise OpError(f'cannot convert {obj} to REAL constant') |
|
|
|
|
|
def as_string(obj, kind=1): |
|
"""Return object as STRING expression (string literal constant). |
|
""" |
|
return Expr(Op.STRING, (obj, kind)) |
|
|
|
|
|
def as_array(obj): |
|
"""Return object as ARRAY expression (array constant). |
|
""" |
|
if isinstance(obj, Expr): |
|
obj = obj, |
|
return Expr(Op.ARRAY, obj) |
|
|
|
|
|
def as_complex(real, imag=0): |
|
"""Return object as COMPLEX expression (complex literal constant). |
|
""" |
|
return Expr(Op.COMPLEX, (as_expr(real), as_expr(imag))) |
|
|
|
|
|
def as_apply(func, *args, **kwargs): |
|
"""Return object as APPLY expression (function call, constructor, etc.) |
|
""" |
|
return Expr(Op.APPLY, |
|
(func, tuple(map(as_expr, args)), |
|
dict((k, as_expr(v)) for k, v in kwargs.items()))) |
|
|
|
|
|
def as_ternary(cond, expr1, expr2): |
|
"""Return object as TERNARY expression (cond?expr1:expr2). |
|
""" |
|
return Expr(Op.TERNARY, (cond, expr1, expr2)) |
|
|
|
|
|
def as_ref(expr): |
|
"""Return object as referencing expression. |
|
""" |
|
return Expr(Op.REF, expr) |
|
|
|
|
|
def as_deref(expr): |
|
"""Return object as dereferencing expression. |
|
""" |
|
return Expr(Op.DEREF, expr) |
|
|
|
|
|
def as_eq(left, right): |
|
return Expr(Op.RELATIONAL, (RelOp.EQ, left, right)) |
|
|
|
|
|
def as_ne(left, right): |
|
return Expr(Op.RELATIONAL, (RelOp.NE, left, right)) |
|
|
|
|
|
def as_lt(left, right): |
|
return Expr(Op.RELATIONAL, (RelOp.LT, left, right)) |
|
|
|
|
|
def as_le(left, right): |
|
return Expr(Op.RELATIONAL, (RelOp.LE, left, right)) |
|
|
|
|
|
def as_gt(left, right): |
|
return Expr(Op.RELATIONAL, (RelOp.GT, left, right)) |
|
|
|
|
|
def as_ge(left, right): |
|
return Expr(Op.RELATIONAL, (RelOp.GE, left, right)) |
|
|
|
|
|
def as_terms(obj): |
|
"""Return expression as TERMS expression. |
|
""" |
|
if isinstance(obj, Expr): |
|
obj = normalize(obj) |
|
if obj.op is Op.TERMS: |
|
return obj |
|
if obj.op is Op.INTEGER: |
|
return Expr(Op.TERMS, {as_integer(1, obj.data[1]): obj.data[0]}) |
|
if obj.op is Op.REAL: |
|
return Expr(Op.TERMS, {as_real(1, obj.data[1]): obj.data[0]}) |
|
return Expr(Op.TERMS, {obj: 1}) |
|
raise OpError(f'cannot convert {type(obj)} to terms Expr') |
|
|
|
|
|
def as_factors(obj): |
|
"""Return expression as FACTORS expression. |
|
""" |
|
if isinstance(obj, Expr): |
|
obj = normalize(obj) |
|
if obj.op is Op.FACTORS: |
|
return obj |
|
if obj.op is Op.TERMS: |
|
if len(obj.data) == 1: |
|
(term, coeff), = obj.data.items() |
|
if coeff == 1: |
|
return Expr(Op.FACTORS, {term: 1}) |
|
return Expr(Op.FACTORS, {term: 1, Expr.number(coeff): 1}) |
|
if (obj.op is Op.APPLY |
|
and obj.data[0] is ArithOp.DIV |
|
and not obj.data[2]): |
|
return Expr(Op.FACTORS, {obj.data[1][0]: 1, obj.data[1][1]: -1}) |
|
return Expr(Op.FACTORS, {obj: 1}) |
|
raise OpError(f'cannot convert {type(obj)} to terms Expr') |
|
|
|
|
|
def as_term_coeff(obj): |
|
"""Return expression as term-coefficient pair. |
|
""" |
|
if isinstance(obj, Expr): |
|
obj = normalize(obj) |
|
if obj.op is Op.INTEGER: |
|
return as_integer(1, obj.data[1]), obj.data[0] |
|
if obj.op is Op.REAL: |
|
return as_real(1, obj.data[1]), obj.data[0] |
|
if obj.op is Op.TERMS: |
|
if len(obj.data) == 1: |
|
(term, coeff), = obj.data.items() |
|
return term, coeff |
|
|
|
if obj.op is Op.APPLY and obj.data[0] is ArithOp.DIV: |
|
t, c = as_term_coeff(obj.data[1][0]) |
|
return as_apply(ArithOp.DIV, t, obj.data[1][1]), c |
|
return obj, 1 |
|
raise OpError(f'cannot convert {type(obj)} to term and coeff') |
|
|
|
|
|
def as_numer_denom(obj): |
|
"""Return expression as numer-denom pair. |
|
""" |
|
if isinstance(obj, Expr): |
|
obj = normalize(obj) |
|
if obj.op in (Op.INTEGER, Op.REAL, Op.COMPLEX, Op.SYMBOL, |
|
Op.INDEXING, Op.TERNARY): |
|
return obj, as_number(1) |
|
elif obj.op is Op.APPLY: |
|
if obj.data[0] is ArithOp.DIV and not obj.data[2]: |
|
numers, denoms = map(as_numer_denom, obj.data[1]) |
|
return numers[0] * denoms[1], numers[1] * denoms[0] |
|
return obj, as_number(1) |
|
elif obj.op is Op.TERMS: |
|
numers, denoms = [], [] |
|
for term, coeff in obj.data.items(): |
|
n, d = as_numer_denom(term) |
|
n = n * coeff |
|
numers.append(n) |
|
denoms.append(d) |
|
numer, denom = as_number(0), as_number(1) |
|
for i in range(len(numers)): |
|
n = numers[i] |
|
for j in range(len(numers)): |
|
if i != j: |
|
n *= denoms[j] |
|
numer += n |
|
denom *= denoms[i] |
|
if denom.op in (Op.INTEGER, Op.REAL) and denom.data[0] < 0: |
|
numer, denom = -numer, -denom |
|
return numer, denom |
|
elif obj.op is Op.FACTORS: |
|
numer, denom = as_number(1), as_number(1) |
|
for b, e in obj.data.items(): |
|
bnumer, bdenom = as_numer_denom(b) |
|
if e > 0: |
|
numer *= bnumer ** e |
|
denom *= bdenom ** e |
|
elif e < 0: |
|
numer *= bdenom ** (-e) |
|
denom *= bnumer ** (-e) |
|
return numer, denom |
|
raise OpError(f'cannot convert {type(obj)} to numer and denom') |
|
|
|
|
|
def _counter(): |
|
|
|
counter = 0 |
|
while True: |
|
counter += 1 |
|
yield counter |
|
|
|
|
|
COUNTER = _counter() |
|
|
|
|
|
def eliminate_quotes(s): |
|
"""Replace quoted substrings of input string. |
|
|
|
Return a new string and a mapping of replacements. |
|
""" |
|
d = {} |
|
|
|
def repl(m): |
|
kind, value = m.groups()[:2] |
|
if kind: |
|
|
|
kind = kind[:-1] |
|
p = {"'": "SINGLE", '"': "DOUBLE"}[value[0]] |
|
k = f'{kind}@__f2py_QUOTES_{p}_{COUNTER.__next__()}@' |
|
d[k] = value |
|
return k |
|
|
|
new_s = re.sub(r'({kind}_|)({single_quoted}|{double_quoted})'.format( |
|
kind=r'\w[\w\d_]*', |
|
single_quoted=r"('([^'\\]|(\\.))*')", |
|
double_quoted=r'("([^"\\]|(\\.))*")'), |
|
repl, s) |
|
|
|
assert '"' not in new_s |
|
assert "'" not in new_s |
|
|
|
return new_s, d |
|
|
|
|
|
def insert_quotes(s, d): |
|
"""Inverse of eliminate_quotes. |
|
""" |
|
for k, v in d.items(): |
|
kind = k[:k.find('@')] |
|
if kind: |
|
kind += '_' |
|
s = s.replace(k, kind + v) |
|
return s |
|
|
|
|
|
def replace_parenthesis(s): |
|
"""Replace substrings of input that are enclosed in parenthesis. |
|
|
|
Return a new string and a mapping of replacements. |
|
""" |
|
|
|
|
|
|
|
|
|
|
|
left, right = None, None |
|
mn_i = len(s) |
|
for left_, right_ in (('(/', '/)'), |
|
'()', |
|
'{}', |
|
'[]'): |
|
i = s.find(left_) |
|
if i == -1: |
|
continue |
|
if i < mn_i: |
|
mn_i = i |
|
left, right = left_, right_ |
|
|
|
if left is None: |
|
return s, {} |
|
|
|
i = mn_i |
|
j = s.find(right, i) |
|
|
|
while s.count(left, i + 1, j) != s.count(right, i + 1, j): |
|
j = s.find(right, j + 1) |
|
if j == -1: |
|
raise ValueError(f'Mismatch of {left+right} parenthesis in {s!r}') |
|
|
|
p = {'(': 'ROUND', '[': 'SQUARE', '{': 'CURLY', '(/': 'ROUNDDIV'}[left] |
|
|
|
k = f'@__f2py_PARENTHESIS_{p}_{COUNTER.__next__()}@' |
|
v = s[i+len(left):j] |
|
r, d = replace_parenthesis(s[j+len(right):]) |
|
d[k] = v |
|
return s[:i] + k + r, d |
|
|
|
|
|
def _get_parenthesis_kind(s): |
|
assert s.startswith('@__f2py_PARENTHESIS_'), s |
|
return s.split('_')[4] |
|
|
|
|
|
def unreplace_parenthesis(s, d): |
|
"""Inverse of replace_parenthesis. |
|
""" |
|
for k, v in d.items(): |
|
p = _get_parenthesis_kind(k) |
|
left = dict(ROUND='(', SQUARE='[', CURLY='{', ROUNDDIV='(/')[p] |
|
right = dict(ROUND=')', SQUARE=']', CURLY='}', ROUNDDIV='/)')[p] |
|
s = s.replace(k, left + v + right) |
|
return s |
|
|
|
|
|
def fromstring(s, language=Language.C): |
|
"""Create an expression from a string. |
|
|
|
This is a "lazy" parser, that is, only arithmetic operations are |
|
resolved, non-arithmetic operations are treated as symbols. |
|
""" |
|
r = _FromStringWorker(language=language).parse(s) |
|
if isinstance(r, Expr): |
|
return r |
|
raise ValueError(f'failed to parse `{s}` to Expr instance: got `{r}`') |
|
|
|
|
|
class _Pair: |
|
|
|
|
|
def __init__(self, left, right): |
|
self.left = left |
|
self.right = right |
|
|
|
def substitute(self, symbols_map): |
|
left, right = self.left, self.right |
|
if isinstance(left, Expr): |
|
left = left.substitute(symbols_map) |
|
if isinstance(right, Expr): |
|
right = right.substitute(symbols_map) |
|
return _Pair(left, right) |
|
|
|
def __repr__(self): |
|
return f'{type(self).__name__}({self.left}, {self.right})' |
|
|
|
|
|
class _FromStringWorker: |
|
|
|
def __init__(self, language=Language.C): |
|
self.original = None |
|
self.quotes_map = None |
|
self.language = language |
|
|
|
def finalize_string(self, s): |
|
return insert_quotes(s, self.quotes_map) |
|
|
|
def parse(self, inp): |
|
self.original = inp |
|
unquoted, self.quotes_map = eliminate_quotes(inp) |
|
return self.process(unquoted) |
|
|
|
def process(self, s, context='expr'): |
|
"""Parse string within the given context. |
|
|
|
The context may define the result in case of ambiguous |
|
expressions. For instance, consider expressions `f(x, y)` and |
|
`(x, y) + (a, b)` where `f` is a function and pair `(x, y)` |
|
denotes complex number. Specifying context as "args" or |
|
"expr", the subexpression `(x, y)` will be parse to an |
|
argument list or to a complex number, respectively. |
|
""" |
|
if isinstance(s, (list, tuple)): |
|
return type(s)(self.process(s_, context) for s_ in s) |
|
|
|
assert isinstance(s, str), (type(s), s) |
|
|
|
|
|
r, raw_symbols_map = replace_parenthesis(s) |
|
r = r.strip() |
|
|
|
def restore(r): |
|
|
|
if isinstance(r, (list, tuple)): |
|
return type(r)(map(restore, r)) |
|
return unreplace_parenthesis(r, raw_symbols_map) |
|
|
|
|
|
if ',' in r: |
|
operands = restore(r.split(',')) |
|
if context == 'args': |
|
return tuple(self.process(operands)) |
|
if context == 'expr': |
|
if len(operands) == 2: |
|
|
|
return as_complex(*self.process(operands)) |
|
raise NotImplementedError( |
|
f'parsing comma-separated list (context={context}): {r}') |
|
|
|
|
|
m = re.match(r'\A([^?]+)[?]([^:]+)[:](.+)\Z', r) |
|
if m: |
|
assert context == 'expr', context |
|
oper, expr1, expr2 = restore(m.groups()) |
|
oper = self.process(oper) |
|
expr1 = self.process(expr1) |
|
expr2 = self.process(expr2) |
|
return as_ternary(oper, expr1, expr2) |
|
|
|
|
|
if self.language is Language.Fortran: |
|
m = re.match( |
|
r'\A(.+)\s*[.](eq|ne|lt|le|gt|ge)[.]\s*(.+)\Z', r, re.I) |
|
else: |
|
m = re.match( |
|
r'\A(.+)\s*([=][=]|[!][=]|[<][=]|[<]|[>][=]|[>])\s*(.+)\Z', r) |
|
if m: |
|
left, rop, right = m.groups() |
|
if self.language is Language.Fortran: |
|
rop = '.' + rop + '.' |
|
left, right = self.process(restore((left, right))) |
|
rop = RelOp.fromstring(rop, language=self.language) |
|
return Expr(Op.RELATIONAL, (rop, left, right)) |
|
|
|
|
|
m = re.match(r'\A(\w[\w\d_]*)\s*[=](.*)\Z', r) |
|
if m: |
|
keyname, value = m.groups() |
|
value = restore(value) |
|
return _Pair(keyname, self.process(value)) |
|
|
|
|
|
operands = re.split(r'((?<!\d[edED])[+-])', r) |
|
if len(operands) > 1: |
|
result = self.process(restore(operands[0] or '0')) |
|
for op, operand in zip(operands[1::2], operands[2::2]): |
|
operand = self.process(restore(operand)) |
|
op = op.strip() |
|
if op == '+': |
|
result += operand |
|
else: |
|
assert op == '-' |
|
result -= operand |
|
return result |
|
|
|
|
|
if self.language is Language.Fortran and '//' in r: |
|
operands = restore(r.split('//')) |
|
return Expr(Op.CONCAT, |
|
tuple(self.process(operands))) |
|
|
|
|
|
operands = re.split(r'(?<=[@\w\d_])\s*([*]|/)', |
|
(r if self.language is Language.C |
|
else r.replace('**', '@__f2py_DOUBLE_STAR@'))) |
|
if len(operands) > 1: |
|
operands = restore(operands) |
|
if self.language is not Language.C: |
|
operands = [operand.replace('@__f2py_DOUBLE_STAR@', '**') |
|
for operand in operands] |
|
|
|
result = self.process(operands[0]) |
|
for op, operand in zip(operands[1::2], operands[2::2]): |
|
operand = self.process(operand) |
|
op = op.strip() |
|
if op == '*': |
|
result *= operand |
|
else: |
|
assert op == '/' |
|
result /= operand |
|
return result |
|
|
|
|
|
if r.startswith('*') or r.startswith('&'): |
|
op = {'*': Op.DEREF, '&': Op.REF}[r[0]] |
|
operand = self.process(restore(r[1:])) |
|
return Expr(op, operand) |
|
|
|
|
|
if self.language is not Language.C and '**' in r: |
|
operands = list(reversed(restore(r.split('**')))) |
|
result = self.process(operands[0]) |
|
for operand in operands[1:]: |
|
operand = self.process(operand) |
|
result = operand ** result |
|
return result |
|
|
|
|
|
m = re.match(r'\A({digit_string})({kind}|)\Z'.format( |
|
digit_string=r'\d+', |
|
kind=r'_(\d+|\w[\w\d_]*)'), r) |
|
if m: |
|
value, _, kind = m.groups() |
|
if kind and kind.isdigit(): |
|
kind = int(kind) |
|
return as_integer(int(value), kind or 4) |
|
|
|
|
|
m = re.match(r'\A({significant}({exponent}|)|\d+{exponent})({kind}|)\Z' |
|
.format( |
|
significant=r'[.]\d+|\d+[.]\d*', |
|
exponent=r'[edED][+-]?\d+', |
|
kind=r'_(\d+|\w[\w\d_]*)'), r) |
|
if m: |
|
value, _, _, kind = m.groups() |
|
if kind and kind.isdigit(): |
|
kind = int(kind) |
|
value = value.lower() |
|
if 'd' in value: |
|
return as_real(float(value.replace('d', 'e')), kind or 8) |
|
return as_real(float(value), kind or 4) |
|
|
|
|
|
if r in self.quotes_map: |
|
kind = r[:r.find('@')] |
|
return as_string(self.quotes_map[r], kind or 1) |
|
|
|
|
|
|
|
if r in raw_symbols_map: |
|
paren = _get_parenthesis_kind(r) |
|
items = self.process(restore(raw_symbols_map[r]), |
|
'expr' if paren == 'ROUND' else 'args') |
|
if paren == 'ROUND': |
|
if isinstance(items, Expr): |
|
return items |
|
if paren in ['ROUNDDIV', 'SQUARE']: |
|
|
|
if isinstance(items, Expr): |
|
items = (items,) |
|
return as_array(items) |
|
|
|
|
|
m = re.match(r'\A(.+)\s*(@__f2py_PARENTHESIS_(ROUND|SQUARE)_\d+@)\Z', |
|
r) |
|
if m: |
|
target, args, paren = m.groups() |
|
target = self.process(restore(target)) |
|
args = self.process(restore(args)[1:-1], 'args') |
|
if not isinstance(args, tuple): |
|
args = args, |
|
if paren == 'ROUND': |
|
kwargs = dict((a.left, a.right) for a in args |
|
if isinstance(a, _Pair)) |
|
args = tuple(a for a in args if not isinstance(a, _Pair)) |
|
|
|
return as_apply(target, *args, **kwargs) |
|
else: |
|
|
|
|
|
assert paren == 'SQUARE' |
|
return target[args] |
|
|
|
|
|
m = re.match(r'\A\w[\w\d_]*\Z', r) |
|
if m: |
|
return as_symbol(r) |
|
|
|
|
|
r = self.finalize_string(restore(r)) |
|
ewarn( |
|
f'fromstring: treating {r!r} as symbol (original={self.original})') |
|
return as_symbol(r) |
|
|