Path: blob/master/ invest-robot-contest_TinkoffBotTwitch-main/venv/lib/python3.8/site-packages/numpy/f2py/symbolic.py
7796 views
"""Fortran/C symbolic expressions12References:3- J3/21-007: Draft Fortran 202x. https://j3-fortran.org/doc/year/21/21-007.pdf4"""56# To analyze Fortran expressions to solve dimensions specifications,7# for instances, we implement a minimal symbolic engine for parsing8# expressions into a tree of expression instances. As a first9# instance, we care only about arithmetic expressions involving10# integers and operations like addition (+), subtraction (-),11# multiplication (*), division (Fortran / is Python //, Fortran // is12# concatenate), and exponentiation (**). In addition, .pyf files may13# contain C expressions that support here is implemented as well.14#15# TODO: support logical constants (Op.BOOLEAN)16# TODO: support logical operators (.AND., ...)17# TODO: support defined operators (.MYOP., ...)18#19__all__ = ['Expr']202122import re23import warnings24from enum import Enum25from math import gcd262728class Language(Enum):29"""30Used as Expr.tostring language argument.31"""32Python = 033Fortran = 134C = 2353637class Op(Enum):38"""39Used as Expr op attribute.40"""41INTEGER = 1042REAL = 1243COMPLEX = 1544STRING = 2045ARRAY = 3046SYMBOL = 4047TERNARY = 10048APPLY = 20049INDEXING = 21050CONCAT = 22051RELATIONAL = 30052TERMS = 100053FACTORS = 200054REF = 300055DEREF = 3001565758class RelOp(Enum):59"""60Used in Op.RELATIONAL expression to specify the function part.61"""62EQ = 163NE = 264LT = 365LE = 466GT = 567GE = 66869@classmethod70def fromstring(cls, s, language=Language.C):71if language is Language.Fortran:72return {'.eq.': RelOp.EQ, '.ne.': RelOp.NE,73'.lt.': RelOp.LT, '.le.': RelOp.LE,74'.gt.': RelOp.GT, '.ge.': RelOp.GE}[s.lower()]75return {'==': RelOp.EQ, '!=': RelOp.NE, '<': RelOp.LT,76'<=': RelOp.LE, '>': RelOp.GT, '>=': RelOp.GE}[s]7778def tostring(self, language=Language.C):79if language is Language.Fortran:80return {RelOp.EQ: '.eq.', RelOp.NE: '.ne.',81RelOp.LT: '.lt.', RelOp.LE: '.le.',82RelOp.GT: '.gt.', RelOp.GE: '.ge.'}[self]83return {RelOp.EQ: '==', RelOp.NE: '!=',84RelOp.LT: '<', RelOp.LE: '<=',85RelOp.GT: '>', RelOp.GE: '>='}[self]868788class ArithOp(Enum):89"""90Used in Op.APPLY expression to specify the function part.91"""92POS = 193NEG = 294ADD = 395SUB = 496MUL = 597DIV = 698POW = 799100101class OpError(Exception):102pass103104105class Precedence(Enum):106"""107Used as Expr.tostring precedence argument.108"""109ATOM = 0110POWER = 1111UNARY = 2112PRODUCT = 3113SUM = 4114LT = 6115EQ = 7116LAND = 11117LOR = 12118TERNARY = 13119ASSIGN = 14120TUPLE = 15121NONE = 100122123124integer_types = (int,)125number_types = (int, float)126127128def _pairs_add(d, k, v):129# Internal utility method for updating terms and factors data.130c = d.get(k)131if c is None:132d[k] = v133else:134c = c + v135if c:136d[k] = c137else:138del d[k]139140141class ExprWarning(UserWarning):142pass143144145def ewarn(message):146warnings.warn(message, ExprWarning, stacklevel=2)147148149class Expr:150"""Represents a Fortran expression as a op-data pair.151152Expr instances are hashable and sortable.153"""154155@staticmethod156def parse(s, language=Language.C):157"""Parse a Fortran expression to a Expr.158"""159return fromstring(s, language=language)160161def __init__(self, op, data):162assert isinstance(op, Op)163164# sanity checks165if op is Op.INTEGER:166# data is a 2-tuple of numeric object and a kind value167# (default is 4)168assert isinstance(data, tuple) and len(data) == 2169assert isinstance(data[0], int)170assert isinstance(data[1], (int, str)), data171elif op is Op.REAL:172# data is a 2-tuple of numeric object and a kind value173# (default is 4)174assert isinstance(data, tuple) and len(data) == 2175assert isinstance(data[0], float)176assert isinstance(data[1], (int, str)), data177elif op is Op.COMPLEX:178# data is a 2-tuple of constant expressions179assert isinstance(data, tuple) and len(data) == 2180elif op is Op.STRING:181# data is a 2-tuple of quoted string and a kind value182# (default is 1)183assert isinstance(data, tuple) and len(data) == 2184assert (isinstance(data[0], str)185and data[0][::len(data[0])-1] in ('""', "''", '@@'))186assert isinstance(data[1], (int, str)), data187elif op is Op.SYMBOL:188# data is any hashable object189assert hash(data) is not None190elif op in (Op.ARRAY, Op.CONCAT):191# data is a tuple of expressions192assert isinstance(data, tuple)193assert all(isinstance(item, Expr) for item in data), data194elif op in (Op.TERMS, Op.FACTORS):195# data is {<term|base>:<coeff|exponent>} where dict values196# are nonzero Python integers197assert isinstance(data, dict)198elif op is Op.APPLY:199# data is (<function>, <operands>, <kwoperands>) where200# operands are Expr instances201assert isinstance(data, tuple) and len(data) == 3202# function is any hashable object203assert hash(data[0]) is not None204assert isinstance(data[1], tuple)205assert isinstance(data[2], dict)206elif op is Op.INDEXING:207# data is (<object>, <indices>)208assert isinstance(data, tuple) and len(data) == 2209# function is any hashable object210assert hash(data[0]) is not None211elif op is Op.TERNARY:212# data is (<cond>, <expr1>, <expr2>)213assert isinstance(data, tuple) and len(data) == 3214elif op in (Op.REF, Op.DEREF):215# data is Expr instance216assert isinstance(data, Expr)217elif op is Op.RELATIONAL:218# data is (<relop>, <left>, <right>)219assert isinstance(data, tuple) and len(data) == 3220else:221raise NotImplementedError(222f'unknown op or missing sanity check: {op}')223224self.op = op225self.data = data226227def __eq__(self, other):228return (isinstance(other, Expr)229and self.op is other.op230and self.data == other.data)231232def __hash__(self):233if self.op in (Op.TERMS, Op.FACTORS):234data = tuple(sorted(self.data.items()))235elif self.op is Op.APPLY:236data = self.data[:2] + tuple(sorted(self.data[2].items()))237else:238data = self.data239return hash((self.op, data))240241def __lt__(self, other):242if isinstance(other, Expr):243if self.op is not other.op:244return self.op.value < other.op.value245if self.op in (Op.TERMS, Op.FACTORS):246return (tuple(sorted(self.data.items()))247< tuple(sorted(other.data.items())))248if self.op is Op.APPLY:249if self.data[:2] != other.data[:2]:250return self.data[:2] < other.data[:2]251return tuple(sorted(self.data[2].items())) < tuple(252sorted(other.data[2].items()))253return self.data < other.data254return NotImplemented255256def __le__(self, other): return self == other or self < other257258def __gt__(self, other): return not (self <= other)259260def __ge__(self, other): return not (self < other)261262def __repr__(self):263return f'{type(self).__name__}({self.op}, {self.data!r})'264265def __str__(self):266return self.tostring()267268def tostring(self, parent_precedence=Precedence.NONE,269language=Language.Fortran):270"""Return a string representation of Expr.271"""272if self.op in (Op.INTEGER, Op.REAL):273precedence = (Precedence.SUM if self.data[0] < 0274else Precedence.ATOM)275r = str(self.data[0]) + (f'_{self.data[1]}'276if self.data[1] != 4 else '')277elif self.op is Op.COMPLEX:278r = ', '.join(item.tostring(Precedence.TUPLE, language=language)279for item in self.data)280r = '(' + r + ')'281precedence = Precedence.ATOM282elif self.op is Op.SYMBOL:283precedence = Precedence.ATOM284r = str(self.data)285elif self.op is Op.STRING:286r = self.data[0]287if self.data[1] != 1:288r = self.data[1] + '_' + r289precedence = Precedence.ATOM290elif self.op is Op.ARRAY:291r = ', '.join(item.tostring(Precedence.TUPLE, language=language)292for item in self.data)293r = '[' + r + ']'294precedence = Precedence.ATOM295elif self.op is Op.TERMS:296terms = []297for term, coeff in sorted(self.data.items()):298if coeff < 0:299op = ' - '300coeff = -coeff301else:302op = ' + '303if coeff == 1:304term = term.tostring(Precedence.SUM, language=language)305else:306if term == as_number(1):307term = str(coeff)308else:309term = f'{coeff} * ' + term.tostring(310Precedence.PRODUCT, language=language)311if terms:312terms.append(op)313elif op == ' - ':314terms.append('-')315terms.append(term)316r = ''.join(terms) or '0'317precedence = Precedence.SUM if terms else Precedence.ATOM318elif self.op is Op.FACTORS:319factors = []320tail = []321for base, exp in sorted(self.data.items()):322op = ' * '323if exp == 1:324factor = base.tostring(Precedence.PRODUCT,325language=language)326elif language is Language.C:327if exp in range(2, 10):328factor = base.tostring(Precedence.PRODUCT,329language=language)330factor = ' * '.join([factor] * exp)331elif exp in range(-10, 0):332factor = base.tostring(Precedence.PRODUCT,333language=language)334tail += [factor] * -exp335continue336else:337factor = base.tostring(Precedence.TUPLE,338language=language)339factor = f'pow({factor}, {exp})'340else:341factor = base.tostring(Precedence.POWER,342language=language) + f' ** {exp}'343if factors:344factors.append(op)345factors.append(factor)346if tail:347if not factors:348factors += ['1']349factors += ['/', '(', ' * '.join(tail), ')']350r = ''.join(factors) or '1'351precedence = Precedence.PRODUCT if factors else Precedence.ATOM352elif self.op is Op.APPLY:353name, args, kwargs = self.data354if name is ArithOp.DIV and language is Language.C:355numer, denom = [arg.tostring(Precedence.PRODUCT,356language=language)357for arg in args]358r = f'{numer} / {denom}'359precedence = Precedence.PRODUCT360else:361args = [arg.tostring(Precedence.TUPLE, language=language)362for arg in args]363args += [k + '=' + v.tostring(Precedence.NONE)364for k, v in kwargs.items()]365r = f'{name}({", ".join(args)})'366precedence = Precedence.ATOM367elif self.op is Op.INDEXING:368name = self.data[0]369args = [arg.tostring(Precedence.TUPLE, language=language)370for arg in self.data[1:]]371r = f'{name}[{", ".join(args)}]'372precedence = Precedence.ATOM373elif self.op is Op.CONCAT:374args = [arg.tostring(Precedence.PRODUCT, language=language)375for arg in self.data]376r = " // ".join(args)377precedence = Precedence.PRODUCT378elif self.op is Op.TERNARY:379cond, expr1, expr2 = [a.tostring(Precedence.TUPLE,380language=language)381for a in self.data]382if language is Language.C:383r = f'({cond}?{expr1}:{expr2})'384elif language is Language.Python:385r = f'({expr1} if {cond} else {expr2})'386elif language is Language.Fortran:387r = f'merge({expr1}, {expr2}, {cond})'388else:389raise NotImplementedError(390f'tostring for {self.op} and {language}')391precedence = Precedence.ATOM392elif self.op is Op.REF:393r = '&' + self.data.tostring(Precedence.UNARY, language=language)394precedence = Precedence.UNARY395elif self.op is Op.DEREF:396r = '*' + self.data.tostring(Precedence.UNARY, language=language)397precedence = Precedence.UNARY398elif self.op is Op.RELATIONAL:399rop, left, right = self.data400precedence = (Precedence.EQ if rop in (RelOp.EQ, RelOp.NE)401else Precedence.LT)402left = left.tostring(precedence, language=language)403right = right.tostring(precedence, language=language)404rop = rop.tostring(language=language)405r = f'{left} {rop} {right}'406else:407raise NotImplementedError(f'tostring for op {self.op}')408if parent_precedence.value < precedence.value:409# If parent precedence is higher than operand precedence,410# operand will be enclosed in parenthesis.411return '(' + r + ')'412return r413414def __pos__(self):415return self416417def __neg__(self):418return self * -1419420def __add__(self, other):421other = as_expr(other)422if isinstance(other, Expr):423if self.op is other.op:424if self.op in (Op.INTEGER, Op.REAL):425return as_number(426self.data[0] + other.data[0],427max(self.data[1], other.data[1]))428if self.op is Op.COMPLEX:429r1, i1 = self.data430r2, i2 = other.data431return as_complex(r1 + r2, i1 + i2)432if self.op is Op.TERMS:433r = Expr(self.op, dict(self.data))434for k, v in other.data.items():435_pairs_add(r.data, k, v)436return normalize(r)437if self.op is Op.COMPLEX and other.op in (Op.INTEGER, Op.REAL):438return self + as_complex(other)439elif self.op in (Op.INTEGER, Op.REAL) and other.op is Op.COMPLEX:440return as_complex(self) + other441elif self.op is Op.REAL and other.op is Op.INTEGER:442return self + as_real(other, kind=self.data[1])443elif self.op is Op.INTEGER and other.op is Op.REAL:444return as_real(self, kind=other.data[1]) + other445return as_terms(self) + as_terms(other)446return NotImplemented447448def __radd__(self, other):449if isinstance(other, number_types):450return as_number(other) + self451return NotImplemented452453def __sub__(self, other):454return self + (-other)455456def __rsub__(self, other):457if isinstance(other, number_types):458return as_number(other) - self459return NotImplemented460461def __mul__(self, other):462other = as_expr(other)463if isinstance(other, Expr):464if self.op is other.op:465if self.op in (Op.INTEGER, Op.REAL):466return as_number(self.data[0] * other.data[0],467max(self.data[1], other.data[1]))468elif self.op is Op.COMPLEX:469r1, i1 = self.data470r2, i2 = other.data471return as_complex(r1 * r2 - i1 * i2, r1 * i2 + r2 * i1)472473if self.op is Op.FACTORS:474r = Expr(self.op, dict(self.data))475for k, v in other.data.items():476_pairs_add(r.data, k, v)477return normalize(r)478elif self.op is Op.TERMS:479r = Expr(self.op, {})480for t1, c1 in self.data.items():481for t2, c2 in other.data.items():482_pairs_add(r.data, t1 * t2, c1 * c2)483return normalize(r)484485if self.op is Op.COMPLEX and other.op in (Op.INTEGER, Op.REAL):486return self * as_complex(other)487elif other.op is Op.COMPLEX and self.op in (Op.INTEGER, Op.REAL):488return as_complex(self) * other489elif self.op is Op.REAL and other.op is Op.INTEGER:490return self * as_real(other, kind=self.data[1])491elif self.op is Op.INTEGER and other.op is Op.REAL:492return as_real(self, kind=other.data[1]) * other493494if self.op is Op.TERMS:495return self * as_terms(other)496elif other.op is Op.TERMS:497return as_terms(self) * other498499return as_factors(self) * as_factors(other)500return NotImplemented501502def __rmul__(self, other):503if isinstance(other, number_types):504return as_number(other) * self505return NotImplemented506507def __pow__(self, other):508other = as_expr(other)509if isinstance(other, Expr):510if other.op is Op.INTEGER:511exponent = other.data[0]512# TODO: other kind not used513if exponent == 0:514return as_number(1)515if exponent == 1:516return self517if exponent > 0:518if self.op is Op.FACTORS:519r = Expr(self.op, {})520for k, v in self.data.items():521r.data[k] = v * exponent522return normalize(r)523return self * (self ** (exponent - 1))524elif exponent != -1:525return (self ** (-exponent)) ** -1526return Expr(Op.FACTORS, {self: exponent})527return as_apply(ArithOp.POW, self, other)528return NotImplemented529530def __truediv__(self, other):531other = as_expr(other)532if isinstance(other, Expr):533# Fortran / is different from Python /:534# - `/` is a truncate operation for integer operands535return normalize(as_apply(ArithOp.DIV, self, other))536return NotImplemented537538def __rtruediv__(self, other):539other = as_expr(other)540if isinstance(other, Expr):541return other / self542return NotImplemented543544def __floordiv__(self, other):545other = as_expr(other)546if isinstance(other, Expr):547# Fortran // is different from Python //:548# - `//` is a concatenate operation for string operands549return normalize(Expr(Op.CONCAT, (self, other)))550return NotImplemented551552def __rfloordiv__(self, other):553other = as_expr(other)554if isinstance(other, Expr):555return other // self556return NotImplemented557558def __call__(self, *args, **kwargs):559# In Fortran, parenthesis () are use for both function call as560# well as indexing operations.561#562# TODO: implement a method for deciding when __call__ should563# return an INDEXING expression.564return as_apply(self, *map(as_expr, args),565**dict((k, as_expr(v)) for k, v in kwargs.items()))566567def __getitem__(self, index):568# Provided to support C indexing operations that .pyf files569# may contain.570index = as_expr(index)571if not isinstance(index, tuple):572index = index,573if len(index) > 1:574ewarn(f'C-index should be a single expression but got `{index}`')575return Expr(Op.INDEXING, (self,) + index)576577def substitute(self, symbols_map):578"""Recursively substitute symbols with values in symbols map.579580Symbols map is a dictionary of symbol-expression pairs.581"""582if self.op is Op.SYMBOL:583value = symbols_map.get(self)584if value is None:585return self586m = re.match(r'\A(@__f2py_PARENTHESIS_(\w+)_\d+@)\Z', self.data)587if m:588# complement to fromstring method589items, paren = m.groups()590if paren in ['ROUNDDIV', 'SQUARE']:591return as_array(value)592assert paren == 'ROUND', (paren, value)593return value594if self.op in (Op.INTEGER, Op.REAL, Op.STRING):595return self596if self.op in (Op.ARRAY, Op.COMPLEX):597return Expr(self.op, tuple(item.substitute(symbols_map)598for item in self.data))599if self.op is Op.CONCAT:600return normalize(Expr(self.op, tuple(item.substitute(symbols_map)601for item in self.data)))602if self.op is Op.TERMS:603r = None604for term, coeff in self.data.items():605if r is None:606r = term.substitute(symbols_map) * coeff607else:608r += term.substitute(symbols_map) * coeff609if r is None:610ewarn('substitute: empty TERMS expression interpreted as'611' int-literal 0')612return as_number(0)613return r614if self.op is Op.FACTORS:615r = None616for base, exponent in self.data.items():617if r is None:618r = base.substitute(symbols_map) ** exponent619else:620r *= base.substitute(symbols_map) ** exponent621if r is None:622ewarn('substitute: empty FACTORS expression interpreted'623' as int-literal 1')624return as_number(1)625return r626if self.op is Op.APPLY:627target, args, kwargs = self.data628if isinstance(target, Expr):629target = target.substitute(symbols_map)630args = tuple(a.substitute(symbols_map) for a in args)631kwargs = dict((k, v.substitute(symbols_map))632for k, v in kwargs.items())633return normalize(Expr(self.op, (target, args, kwargs)))634if self.op is Op.INDEXING:635func = self.data[0]636if isinstance(func, Expr):637func = func.substitute(symbols_map)638args = tuple(a.substitute(symbols_map) for a in self.data[1:])639return normalize(Expr(self.op, (func,) + args))640if self.op is Op.TERNARY:641operands = tuple(a.substitute(symbols_map) for a in self.data)642return normalize(Expr(self.op, operands))643if self.op in (Op.REF, Op.DEREF):644return normalize(Expr(self.op, self.data.substitute(symbols_map)))645if self.op is Op.RELATIONAL:646rop, left, right = self.data647left = left.substitute(symbols_map)648right = right.substitute(symbols_map)649return normalize(Expr(self.op, (rop, left, right)))650raise NotImplementedError(f'substitute method for {self.op}: {self!r}')651652def traverse(self, visit, *args, **kwargs):653"""Traverse expression tree with visit function.654655The visit function is applied to an expression with given args656and kwargs.657658Traverse call returns an expression returned by visit when not659None, otherwise return a new normalized expression with660traverse-visit sub-expressions.661"""662result = visit(self, *args, **kwargs)663if result is not None:664return result665666if self.op in (Op.INTEGER, Op.REAL, Op.STRING, Op.SYMBOL):667return self668elif self.op in (Op.COMPLEX, Op.ARRAY, Op.CONCAT, Op.TERNARY):669return normalize(Expr(self.op, tuple(670item.traverse(visit, *args, **kwargs)671for item in self.data)))672elif self.op in (Op.TERMS, Op.FACTORS):673data = {}674for k, v in self.data.items():675k = k.traverse(visit, *args, **kwargs)676v = (v.traverse(visit, *args, **kwargs)677if isinstance(v, Expr) else v)678if k in data:679v = data[k] + v680data[k] = v681return normalize(Expr(self.op, data))682elif self.op is Op.APPLY:683obj = self.data[0]684func = (obj.traverse(visit, *args, **kwargs)685if isinstance(obj, Expr) else obj)686operands = tuple(operand.traverse(visit, *args, **kwargs)687for operand in self.data[1])688kwoperands = dict((k, v.traverse(visit, *args, **kwargs))689for k, v in self.data[2].items())690return normalize(Expr(self.op, (func, operands, kwoperands)))691elif self.op is Op.INDEXING:692obj = self.data[0]693obj = (obj.traverse(visit, *args, **kwargs)694if isinstance(obj, Expr) else obj)695indices = tuple(index.traverse(visit, *args, **kwargs)696for index in self.data[1:])697return normalize(Expr(self.op, (obj,) + indices))698elif self.op in (Op.REF, Op.DEREF):699return normalize(Expr(self.op,700self.data.traverse(visit, *args, **kwargs)))701elif self.op is Op.RELATIONAL:702rop, left, right = self.data703left = left.traverse(visit, *args, **kwargs)704right = right.traverse(visit, *args, **kwargs)705return normalize(Expr(self.op, (rop, left, right)))706raise NotImplementedError(f'traverse method for {self.op}')707708def contains(self, other):709"""Check if self contains other.710"""711found = []712713def visit(expr, found=found):714if found:715return expr716elif expr == other:717found.append(1)718return expr719720self.traverse(visit)721722return len(found) != 0723724def symbols(self):725"""Return a set of symbols contained in self.726"""727found = set()728729def visit(expr, found=found):730if expr.op is Op.SYMBOL:731found.add(expr)732733self.traverse(visit)734735return found736737def polynomial_atoms(self):738"""Return a set of expressions used as atoms in polynomial self.739"""740found = set()741742def visit(expr, found=found):743if expr.op is Op.FACTORS:744for b in expr.data:745b.traverse(visit)746return expr747if expr.op in (Op.TERMS, Op.COMPLEX):748return749if expr.op is Op.APPLY and isinstance(expr.data[0], ArithOp):750if expr.data[0] is ArithOp.POW:751expr.data[1][0].traverse(visit)752return expr753return754if expr.op in (Op.INTEGER, Op.REAL):755return expr756757found.add(expr)758759if expr.op in (Op.INDEXING, Op.APPLY):760return expr761762self.traverse(visit)763764return found765766def linear_solve(self, symbol):767"""Return a, b such that a * symbol + b == self.768769If self is not linear with respect to symbol, raise RuntimeError.770"""771b = self.substitute({symbol: as_number(0)})772ax = self - b773a = ax.substitute({symbol: as_number(1)})774775zero, _ = as_numer_denom(a * symbol - ax)776777if zero != as_number(0):778raise RuntimeError(f'not a {symbol}-linear equation:'779f' {a} * {symbol} + {b} == {self}')780return a, b781782783def normalize(obj):784"""Normalize Expr and apply basic evaluation methods.785"""786if not isinstance(obj, Expr):787return obj788789if obj.op is Op.TERMS:790d = {}791for t, c in obj.data.items():792if c == 0:793continue794if t.op is Op.COMPLEX and c != 1:795t = t * c796c = 1797if t.op is Op.TERMS:798for t1, c1 in t.data.items():799_pairs_add(d, t1, c1 * c)800else:801_pairs_add(d, t, c)802if len(d) == 0:803# TODO: deterimine correct kind804return as_number(0)805elif len(d) == 1:806(t, c), = d.items()807if c == 1:808return t809return Expr(Op.TERMS, d)810811if obj.op is Op.FACTORS:812coeff = 1813d = {}814for b, e in obj.data.items():815if e == 0:816continue817if b.op is Op.TERMS and isinstance(e, integer_types) and e > 1:818# expand integer powers of sums819b = b * (b ** (e - 1))820e = 1821822if b.op in (Op.INTEGER, Op.REAL):823if e == 1:824coeff *= b.data[0]825elif e > 0:826coeff *= b.data[0] ** e827else:828_pairs_add(d, b, e)829elif b.op is Op.FACTORS:830if e > 0 and isinstance(e, integer_types):831for b1, e1 in b.data.items():832_pairs_add(d, b1, e1 * e)833else:834_pairs_add(d, b, e)835else:836_pairs_add(d, b, e)837if len(d) == 0 or coeff == 0:838# TODO: deterimine correct kind839assert isinstance(coeff, number_types)840return as_number(coeff)841elif len(d) == 1:842(b, e), = d.items()843if e == 1:844t = b845else:846t = Expr(Op.FACTORS, d)847if coeff == 1:848return t849return Expr(Op.TERMS, {t: coeff})850elif coeff == 1:851return Expr(Op.FACTORS, d)852else:853return Expr(Op.TERMS, {Expr(Op.FACTORS, d): coeff})854855if obj.op is Op.APPLY and obj.data[0] is ArithOp.DIV:856dividend, divisor = obj.data[1]857t1, c1 = as_term_coeff(dividend)858t2, c2 = as_term_coeff(divisor)859if isinstance(c1, integer_types) and isinstance(c2, integer_types):860g = gcd(c1, c2)861c1, c2 = c1//g, c2//g862else:863c1, c2 = c1/c2, 1864865if t1.op is Op.APPLY and t1.data[0] is ArithOp.DIV:866numer = t1.data[1][0] * c1867denom = t1.data[1][1] * t2 * c2868return as_apply(ArithOp.DIV, numer, denom)869870if t2.op is Op.APPLY and t2.data[0] is ArithOp.DIV:871numer = t2.data[1][1] * t1 * c1872denom = t2.data[1][0] * c2873return as_apply(ArithOp.DIV, numer, denom)874875d = dict(as_factors(t1).data)876for b, e in as_factors(t2).data.items():877_pairs_add(d, b, -e)878numer, denom = {}, {}879for b, e in d.items():880if e > 0:881numer[b] = e882else:883denom[b] = -e884numer = normalize(Expr(Op.FACTORS, numer)) * c1885denom = normalize(Expr(Op.FACTORS, denom)) * c2886887if denom.op in (Op.INTEGER, Op.REAL) and denom.data[0] == 1:888# TODO: denom kind not used889return numer890return as_apply(ArithOp.DIV, numer, denom)891892if obj.op is Op.CONCAT:893lst = [obj.data[0]]894for s in obj.data[1:]:895last = lst[-1]896if (897last.op is Op.STRING898and s.op is Op.STRING899and last.data[0][0] in '"\''900and s.data[0][0] == last.data[0][-1]901):902new_last = as_string(last.data[0][:-1] + s.data[0][1:],903max(last.data[1], s.data[1]))904lst[-1] = new_last905else:906lst.append(s)907if len(lst) == 1:908return lst[0]909return Expr(Op.CONCAT, tuple(lst))910911if obj.op is Op.TERNARY:912cond, expr1, expr2 = map(normalize, obj.data)913if cond.op is Op.INTEGER:914return expr1 if cond.data[0] else expr2915return Expr(Op.TERNARY, (cond, expr1, expr2))916917return obj918919920def as_expr(obj):921"""Convert non-Expr objects to Expr objects.922"""923if isinstance(obj, complex):924return as_complex(obj.real, obj.imag)925if isinstance(obj, number_types):926return as_number(obj)927if isinstance(obj, str):928# STRING expression holds string with boundary quotes, hence929# applying repr:930return as_string(repr(obj))931if isinstance(obj, tuple):932return tuple(map(as_expr, obj))933return obj934935936def as_symbol(obj):937"""Return object as SYMBOL expression (variable or unparsed expression).938"""939return Expr(Op.SYMBOL, obj)940941942def as_number(obj, kind=4):943"""Return object as INTEGER or REAL constant.944"""945if isinstance(obj, int):946return Expr(Op.INTEGER, (obj, kind))947if isinstance(obj, float):948return Expr(Op.REAL, (obj, kind))949if isinstance(obj, Expr):950if obj.op in (Op.INTEGER, Op.REAL):951return obj952raise OpError(f'cannot convert {obj} to INTEGER or REAL constant')953954955def as_integer(obj, kind=4):956"""Return object as INTEGER constant.957"""958if isinstance(obj, int):959return Expr(Op.INTEGER, (obj, kind))960if isinstance(obj, Expr):961if obj.op is Op.INTEGER:962return obj963raise OpError(f'cannot convert {obj} to INTEGER constant')964965966def as_real(obj, kind=4):967"""Return object as REAL constant.968"""969if isinstance(obj, int):970return Expr(Op.REAL, (float(obj), kind))971if isinstance(obj, float):972return Expr(Op.REAL, (obj, kind))973if isinstance(obj, Expr):974if obj.op is Op.REAL:975return obj976elif obj.op is Op.INTEGER:977return Expr(Op.REAL, (float(obj.data[0]), kind))978raise OpError(f'cannot convert {obj} to REAL constant')979980981def as_string(obj, kind=1):982"""Return object as STRING expression (string literal constant).983"""984return Expr(Op.STRING, (obj, kind))985986987def as_array(obj):988"""Return object as ARRAY expression (array constant).989"""990if isinstance(obj, Expr):991obj = obj,992return Expr(Op.ARRAY, obj)993994995def as_complex(real, imag=0):996"""Return object as COMPLEX expression (complex literal constant).997"""998return Expr(Op.COMPLEX, (as_expr(real), as_expr(imag)))99910001001def as_apply(func, *args, **kwargs):1002"""Return object as APPLY expression (function call, constructor, etc.)1003"""1004return Expr(Op.APPLY,1005(func, tuple(map(as_expr, args)),1006dict((k, as_expr(v)) for k, v in kwargs.items())))100710081009def as_ternary(cond, expr1, expr2):1010"""Return object as TERNARY expression (cond?expr1:expr2).1011"""1012return Expr(Op.TERNARY, (cond, expr1, expr2))101310141015def as_ref(expr):1016"""Return object as referencing expression.1017"""1018return Expr(Op.REF, expr)101910201021def as_deref(expr):1022"""Return object as dereferencing expression.1023"""1024return Expr(Op.DEREF, expr)102510261027def as_eq(left, right):1028return Expr(Op.RELATIONAL, (RelOp.EQ, left, right))102910301031def as_ne(left, right):1032return Expr(Op.RELATIONAL, (RelOp.NE, left, right))103310341035def as_lt(left, right):1036return Expr(Op.RELATIONAL, (RelOp.LT, left, right))103710381039def as_le(left, right):1040return Expr(Op.RELATIONAL, (RelOp.LE, left, right))104110421043def as_gt(left, right):1044return Expr(Op.RELATIONAL, (RelOp.GT, left, right))104510461047def as_ge(left, right):1048return Expr(Op.RELATIONAL, (RelOp.GE, left, right))104910501051def as_terms(obj):1052"""Return expression as TERMS expression.1053"""1054if isinstance(obj, Expr):1055obj = normalize(obj)1056if obj.op is Op.TERMS:1057return obj1058if obj.op is Op.INTEGER:1059return Expr(Op.TERMS, {as_integer(1, obj.data[1]): obj.data[0]})1060if obj.op is Op.REAL:1061return Expr(Op.TERMS, {as_real(1, obj.data[1]): obj.data[0]})1062return Expr(Op.TERMS, {obj: 1})1063raise OpError(f'cannot convert {type(obj)} to terms Expr')106410651066def as_factors(obj):1067"""Return expression as FACTORS expression.1068"""1069if isinstance(obj, Expr):1070obj = normalize(obj)1071if obj.op is Op.FACTORS:1072return obj1073if obj.op is Op.TERMS:1074if len(obj.data) == 1:1075(term, coeff), = obj.data.items()1076if coeff == 1:1077return Expr(Op.FACTORS, {term: 1})1078return Expr(Op.FACTORS, {term: 1, Expr.number(coeff): 1})1079if ((obj.op is Op.APPLY1080and obj.data[0] is ArithOp.DIV1081and not obj.data[2])):1082return Expr(Op.FACTORS, {obj.data[1][0]: 1, obj.data[1][1]: -1})1083return Expr(Op.FACTORS, {obj: 1})1084raise OpError(f'cannot convert {type(obj)} to terms Expr')108510861087def as_term_coeff(obj):1088"""Return expression as term-coefficient pair.1089"""1090if isinstance(obj, Expr):1091obj = normalize(obj)1092if obj.op is Op.INTEGER:1093return as_integer(1, obj.data[1]), obj.data[0]1094if obj.op is Op.REAL:1095return as_real(1, obj.data[1]), obj.data[0]1096if obj.op is Op.TERMS:1097if len(obj.data) == 1:1098(term, coeff), = obj.data.items()1099return term, coeff1100# TODO: find common divisor of coefficients1101if obj.op is Op.APPLY and obj.data[0] is ArithOp.DIV:1102t, c = as_term_coeff(obj.data[1][0])1103return as_apply(ArithOp.DIV, t, obj.data[1][1]), c1104return obj, 11105raise OpError(f'cannot convert {type(obj)} to term and coeff')110611071108def as_numer_denom(obj):1109"""Return expression as numer-denom pair.1110"""1111if isinstance(obj, Expr):1112obj = normalize(obj)1113if obj.op in (Op.INTEGER, Op.REAL, Op.COMPLEX, Op.SYMBOL,1114Op.INDEXING, Op.TERNARY):1115return obj, as_number(1)1116elif obj.op is Op.APPLY:1117if obj.data[0] is ArithOp.DIV and not obj.data[2]:1118numers, denoms = map(as_numer_denom, obj.data[1])1119return numers[0] * denoms[1], numers[1] * denoms[0]1120return obj, as_number(1)1121elif obj.op is Op.TERMS:1122numers, denoms = [], []1123for term, coeff in obj.data.items():1124n, d = as_numer_denom(term)1125n = n * coeff1126numers.append(n)1127denoms.append(d)1128numer, denom = as_number(0), as_number(1)1129for i in range(len(numers)):1130n = numers[i]1131for j in range(len(numers)):1132if i != j:1133n *= denoms[j]1134numer += n1135denom *= denoms[i]1136if denom.op in (Op.INTEGER, Op.REAL) and denom.data[0] < 0:1137numer, denom = -numer, -denom1138return numer, denom1139elif obj.op is Op.FACTORS:1140numer, denom = as_number(1), as_number(1)1141for b, e in obj.data.items():1142bnumer, bdenom = as_numer_denom(b)1143if e > 0:1144numer *= bnumer ** e1145denom *= bdenom ** e1146elif e < 0:1147numer *= bdenom ** (-e)1148denom *= bnumer ** (-e)1149return numer, denom1150raise OpError(f'cannot convert {type(obj)} to numer and denom')115111521153def _counter():1154# Used internally to generate unique dummy symbols1155counter = 01156while True:1157counter += 11158yield counter115911601161COUNTER = _counter()116211631164def eliminate_quotes(s):1165"""Replace quoted substrings of input string.11661167Return a new string and a mapping of replacements.1168"""1169d = {}11701171def repl(m):1172kind, value = m.groups()[:2]1173if kind:1174# remove trailing underscore1175kind = kind[:-1]1176p = {"'": "SINGLE", '"': "DOUBLE"}[value[0]]1177k = f'{kind}@__f2py_QUOTES_{p}_{COUNTER.__next__()}@'1178d[k] = value1179return k11801181new_s = re.sub(r'({kind}_|)({single_quoted}|{double_quoted})'.format(1182kind=r'\w[\w\d_]*',1183single_quoted=r"('([^'\\]|(\\.))*')",1184double_quoted=r'("([^"\\]|(\\.))*")'),1185repl, s)11861187assert '"' not in new_s1188assert "'" not in new_s11891190return new_s, d119111921193def insert_quotes(s, d):1194"""Inverse of eliminate_quotes.1195"""1196for k, v in d.items():1197kind = k[:k.find('@')]1198if kind:1199kind += '_'1200s = s.replace(k, kind + v)1201return s120212031204def replace_parenthesis(s):1205"""Replace substrings of input that are enclosed in parenthesis.12061207Return a new string and a mapping of replacements.1208"""1209# Find a parenthesis pair that appears first.12101211# Fortran deliminator are `(`, `)`, `[`, `]`, `(/', '/)`, `/`.1212# We don't handle `/` deliminator because it is not a part of an1213# expression.1214left, right = None, None1215mn_i = len(s)1216for left_, right_ in (('(/', '/)'),1217'()',1218'{}', # to support C literal structs1219'[]'):1220i = s.find(left_)1221if i == -1:1222continue1223if i < mn_i:1224mn_i = i1225left, right = left_, right_12261227if left is None:1228return s, {}12291230i = mn_i1231j = s.find(right, i)12321233while s.count(left, i + 1, j) != s.count(right, i + 1, j):1234j = s.find(right, j + 1)1235if j == -1:1236raise ValueError(f'Mismatch of {left+right} parenthesis in {s!r}')12371238p = {'(': 'ROUND', '[': 'SQUARE', '{': 'CURLY', '(/': 'ROUNDDIV'}[left]12391240k = f'@__f2py_PARENTHESIS_{p}_{COUNTER.__next__()}@'1241v = s[i+len(left):j]1242r, d = replace_parenthesis(s[j+len(right):])1243d[k] = v1244return s[:i] + k + r, d124512461247def _get_parenthesis_kind(s):1248assert s.startswith('@__f2py_PARENTHESIS_'), s1249return s.split('_')[4]125012511252def unreplace_parenthesis(s, d):1253"""Inverse of replace_parenthesis.1254"""1255for k, v in d.items():1256p = _get_parenthesis_kind(k)1257left = dict(ROUND='(', SQUARE='[', CURLY='{', ROUNDDIV='(/')[p]1258right = dict(ROUND=')', SQUARE=']', CURLY='}', ROUNDDIV='/)')[p]1259s = s.replace(k, left + v + right)1260return s126112621263def fromstring(s, language=Language.C):1264"""Create an expression from a string.12651266This is a "lazy" parser, that is, only arithmetic operations are1267resolved, non-arithmetic operations are treated as symbols.1268"""1269r = _FromStringWorker(language=language).parse(s)1270if isinstance(r, Expr):1271return r1272raise ValueError(f'failed to parse `{s}` to Expr instance: got `{r}`')127312741275class _Pair:1276# Internal class to represent a pair of expressions12771278def __init__(self, left, right):1279self.left = left1280self.right = right12811282def substitute(self, symbols_map):1283left, right = self.left, self.right1284if isinstance(left, Expr):1285left = left.substitute(symbols_map)1286if isinstance(right, Expr):1287right = right.substitute(symbols_map)1288return _Pair(left, right)12891290def __repr__(self):1291return f'{type(self).__name__}({self.left}, {self.right})'129212931294class _FromStringWorker:12951296def __init__(self, language=Language.C):1297self.original = None1298self.quotes_map = None1299self.language = language13001301def finalize_string(self, s):1302return insert_quotes(s, self.quotes_map)13031304def parse(self, inp):1305self.original = inp1306unquoted, self.quotes_map = eliminate_quotes(inp)1307return self.process(unquoted)13081309def process(self, s, context='expr'):1310"""Parse string within the given context.13111312The context may define the result in case of ambiguous1313expressions. For instance, consider expressions `f(x, y)` and1314`(x, y) + (a, b)` where `f` is a function and pair `(x, y)`1315denotes complex number. Specifying context as "args" or1316"expr", the subexpression `(x, y)` will be parse to an1317argument list or to a complex number, respectively.1318"""1319if isinstance(s, (list, tuple)):1320return type(s)(self.process(s_, context) for s_ in s)13211322assert isinstance(s, str), (type(s), s)13231324# replace subexpressions in parenthesis with f2py @-names1325r, raw_symbols_map = replace_parenthesis(s)1326r = r.strip()13271328def restore(r):1329# restores subexpressions marked with f2py @-names1330if isinstance(r, (list, tuple)):1331return type(r)(map(restore, r))1332return unreplace_parenthesis(r, raw_symbols_map)13331334# comma-separated tuple1335if ',' in r:1336operands = restore(r.split(','))1337if context == 'args':1338return tuple(self.process(operands))1339if context == 'expr':1340if len(operands) == 2:1341# complex number literal1342return as_complex(*self.process(operands))1343raise NotImplementedError(1344f'parsing comma-separated list (context={context}): {r}')13451346# ternary operation1347m = re.match(r'\A([^?]+)[?]([^:]+)[:](.+)\Z', r)1348if m:1349assert context == 'expr', context1350oper, expr1, expr2 = restore(m.groups())1351oper = self.process(oper)1352expr1 = self.process(expr1)1353expr2 = self.process(expr2)1354return as_ternary(oper, expr1, expr2)13551356# relational expression1357if self.language is Language.Fortran:1358m = re.match(1359r'\A(.+)\s*[.](eq|ne|lt|le|gt|ge)[.]\s*(.+)\Z', r, re.I)1360else:1361m = re.match(1362r'\A(.+)\s*([=][=]|[!][=]|[<][=]|[<]|[>][=]|[>])\s*(.+)\Z', r)1363if m:1364left, rop, right = m.groups()1365if self.language is Language.Fortran:1366rop = '.' + rop + '.'1367left, right = self.process(restore((left, right)))1368rop = RelOp.fromstring(rop, language=self.language)1369return Expr(Op.RELATIONAL, (rop, left, right))13701371# keyword argument1372m = re.match(r'\A(\w[\w\d_]*)\s*[=](.*)\Z', r)1373if m:1374keyname, value = m.groups()1375value = restore(value)1376return _Pair(keyname, self.process(value))13771378# addition/subtraction operations1379operands = re.split(r'((?<!\d[edED])[+-])', r)1380if len(operands) > 1:1381result = self.process(restore(operands[0] or '0'))1382for op, operand in zip(operands[1::2], operands[2::2]):1383operand = self.process(restore(operand))1384op = op.strip()1385if op == '+':1386result += operand1387else:1388assert op == '-'1389result -= operand1390return result13911392# string concatenate operation1393if self.language is Language.Fortran and '//' in r:1394operands = restore(r.split('//'))1395return Expr(Op.CONCAT,1396tuple(self.process(operands)))13971398# multiplication/division operations1399operands = re.split(r'(?<=[@\w\d_])\s*([*]|/)',1400(r if self.language is Language.C1401else r.replace('**', '@__f2py_DOUBLE_STAR@')))1402if len(operands) > 1:1403operands = restore(operands)1404if self.language is not Language.C:1405operands = [operand.replace('@__f2py_DOUBLE_STAR@', '**')1406for operand in operands]1407# Expression is an arithmetic product1408result = self.process(operands[0])1409for op, operand in zip(operands[1::2], operands[2::2]):1410operand = self.process(operand)1411op = op.strip()1412if op == '*':1413result *= operand1414else:1415assert op == '/'1416result /= operand1417return result14181419# referencing/dereferencing1420if r.startswith('*') or r.startswith('&'):1421op = {'*': Op.DEREF, '&': Op.REF}[r[0]]1422operand = self.process(restore(r[1:]))1423return Expr(op, operand)14241425# exponentiation operations1426if self.language is not Language.C and '**' in r:1427operands = list(reversed(restore(r.split('**'))))1428result = self.process(operands[0])1429for operand in operands[1:]:1430operand = self.process(operand)1431result = operand ** result1432return result14331434# int-literal-constant1435m = re.match(r'\A({digit_string})({kind}|)\Z'.format(1436digit_string=r'\d+',1437kind=r'_(\d+|\w[\w\d_]*)'), r)1438if m:1439value, _, kind = m.groups()1440if kind and kind.isdigit():1441kind = int(kind)1442return as_integer(int(value), kind or 4)14431444# real-literal-constant1445m = re.match(r'\A({significant}({exponent}|)|\d+{exponent})({kind}|)\Z'1446.format(1447significant=r'[.]\d+|\d+[.]\d*',1448exponent=r'[edED][+-]?\d+',1449kind=r'_(\d+|\w[\w\d_]*)'), r)1450if m:1451value, _, _, kind = m.groups()1452if kind and kind.isdigit():1453kind = int(kind)1454value = value.lower()1455if 'd' in value:1456return as_real(float(value.replace('d', 'e')), kind or 8)1457return as_real(float(value), kind or 4)14581459# string-literal-constant with kind parameter specification1460if r in self.quotes_map:1461kind = r[:r.find('@')]1462return as_string(self.quotes_map[r], kind or 1)14631464# array constructor or literal complex constant or1465# parenthesized expression1466if r in raw_symbols_map:1467paren = _get_parenthesis_kind(r)1468items = self.process(restore(raw_symbols_map[r]),1469'expr' if paren == 'ROUND' else 'args')1470if paren == 'ROUND':1471if isinstance(items, Expr):1472return items1473if paren in ['ROUNDDIV', 'SQUARE']:1474# Expression is a array constructor1475if isinstance(items, Expr):1476items = (items,)1477return as_array(items)14781479# function call/indexing1480m = re.match(r'\A(.+)\s*(@__f2py_PARENTHESIS_(ROUND|SQUARE)_\d+@)\Z',1481r)1482if m:1483target, args, paren = m.groups()1484target = self.process(restore(target))1485args = self.process(restore(args)[1:-1], 'args')1486if not isinstance(args, tuple):1487args = args,1488if paren == 'ROUND':1489kwargs = dict((a.left, a.right) for a in args1490if isinstance(a, _Pair))1491args = tuple(a for a in args if not isinstance(a, _Pair))1492# Warning: this could also be Fortran indexing operation..1493return as_apply(target, *args, **kwargs)1494else:1495# Expression is a C/Python indexing operation1496# (e.g. used in .pyf files)1497assert paren == 'SQUARE'1498return target[args]14991500# Fortran standard conforming identifier1501m = re.match(r'\A\w[\w\d_]*\Z', r)1502if m:1503return as_symbol(r)15041505# fall-back to symbol1506r = self.finalize_string(restore(r))1507ewarn(1508f'fromstring: treating {r!r} as symbol (original={self.original})')1509return as_symbol(r)151015111512