1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510 |
- """Fortran/C symbolic expressions
- References:
- - J3/21-007: Draft Fortran 202x. https://j3-fortran.org/doc/year/21/21-007.pdf
- """
- # To analyze Fortran expressions to solve dimensions specifications,
- # for instances, we implement a minimal symbolic engine for parsing
- # expressions into a tree of expression instances. As a first
- # instance, we care only about arithmetic expressions involving
- # integers and operations like addition (+), subtraction (-),
- # multiplication (*), division (Fortran / is Python //, Fortran // is
- # concatenate), and exponentiation (**). In addition, .pyf files may
- # contain C expressions that support here is implemented as well.
- #
- # TODO: support logical constants (Op.BOOLEAN)
- # TODO: support logical operators (.AND., ...)
- # TODO: support defined operators (.MYOP., ...)
- #
- __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):
- # Internal utility method for updating terms and factors data.
- 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)
- # sanity checks
- if op is Op.INTEGER:
- # data is a 2-tuple of numeric object and a kind value
- # (default is 4)
- 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:
- # data is a 2-tuple of numeric object and a kind value
- # (default is 4)
- 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:
- # data is a 2-tuple of constant expressions
- assert isinstance(data, tuple) and len(data) == 2
- elif op is Op.STRING:
- # data is a 2-tuple of quoted string and a kind value
- # (default is 1)
- 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:
- # data is any hashable object
- assert hash(data) is not None
- elif op in (Op.ARRAY, Op.CONCAT):
- # data is a tuple of expressions
- assert isinstance(data, tuple)
- assert all(isinstance(item, Expr) for item in data), data
- elif op in (Op.TERMS, Op.FACTORS):
- # data is {<term|base>:<coeff|exponent>} where dict values
- # are nonzero Python integers
- assert isinstance(data, dict)
- elif op is Op.APPLY:
- # data is (<function>, <operands>, <kwoperands>) where
- # operands are Expr instances
- assert isinstance(data, tuple) and len(data) == 3
- # function is any hashable object
- assert hash(data[0]) is not None
- assert isinstance(data[1], tuple)
- assert isinstance(data[2], dict)
- elif op is Op.INDEXING:
- # data is (<object>, <indices>)
- assert isinstance(data, tuple) and len(data) == 2
- # function is any hashable object
- assert hash(data[0]) is not None
- elif op is Op.TERNARY:
- # data is (<cond>, <expr1>, <expr2>)
- assert isinstance(data, tuple) and len(data) == 3
- elif op in (Op.REF, Op.DEREF):
- # data is Expr instance
- assert isinstance(data, Expr)
- elif op is Op.RELATIONAL:
- # data is (<relop>, <left>, <right>)
- 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:
- # If parent precedence is higher than operand precedence,
- # operand will be enclosed in parenthesis.
- 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]
- # TODO: other kind not used
- 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):
- # Fortran / is different from Python /:
- # - `/` is a truncate operation for integer operands
- 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):
- # Fortran // is different from Python //:
- # - `//` is a concatenate operation for string operands
- 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):
- # In Fortran, parenthesis () are use for both function call as
- # well as indexing operations.
- #
- # TODO: implement a method for deciding when __call__ should
- # return an INDEXING expression.
- return as_apply(self, *map(as_expr, args),
- **dict((k, as_expr(v)) for k, v in kwargs.items()))
- def __getitem__(self, index):
- # Provided to support C indexing operations that .pyf files
- # may contain.
- 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:
- # complement to fromstring method
- 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:
- # TODO: determine correct kind
- 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:
- # expand integer powers of sums
- 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:
- # TODO: determine correct kind
- 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:
- # TODO: denom kind not used
- 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):
- # STRING expression holds string with boundary quotes, hence
- # applying repr:
- 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
- # TODO: find common divisor of coefficients
- 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():
- # Used internally to generate unique dummy symbols
- 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:
- # remove trailing underscore
- 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.
- """
- # Find a parenthesis pair that appears first.
- # Fortran deliminator are `(`, `)`, `[`, `]`, `(/', '/)`, `/`.
- # We don't handle `/` deliminator because it is not a part of an
- # expression.
- left, right = None, None
- mn_i = len(s)
- for left_, right_ in (('(/', '/)'),
- '()',
- '{}', # to support C literal structs
- '[]'):
- 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:
- # Internal class to represent a pair of expressions
- 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)
- # replace subexpressions in parenthesis with f2py @-names
- r, raw_symbols_map = replace_parenthesis(s)
- r = r.strip()
- def restore(r):
- # restores subexpressions marked with f2py @-names
- if isinstance(r, (list, tuple)):
- return type(r)(map(restore, r))
- return unreplace_parenthesis(r, raw_symbols_map)
- # comma-separated tuple
- if ',' in r:
- operands = restore(r.split(','))
- if context == 'args':
- return tuple(self.process(operands))
- if context == 'expr':
- if len(operands) == 2:
- # complex number literal
- return as_complex(*self.process(operands))
- raise NotImplementedError(
- f'parsing comma-separated list (context={context}): {r}')
- # ternary operation
- 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)
- # relational expression
- 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))
- # keyword argument
- 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))
- # addition/subtraction operations
- 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
- # string concatenate operation
- if self.language is Language.Fortran and '//' in r:
- operands = restore(r.split('//'))
- return Expr(Op.CONCAT,
- tuple(self.process(operands)))
- # multiplication/division operations
- 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]
- # Expression is an arithmetic product
- 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
- # referencing/dereferencing
- if r.startswith('*') or r.startswith('&'):
- op = {'*': Op.DEREF, '&': Op.REF}[r[0]]
- operand = self.process(restore(r[1:]))
- return Expr(op, operand)
- # exponentiation operations
- 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
- # int-literal-constant
- 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)
- # real-literal-constant
- 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)
- # string-literal-constant with kind parameter specification
- if r in self.quotes_map:
- kind = r[:r.find('@')]
- return as_string(self.quotes_map[r], kind or 1)
- # array constructor or literal complex constant or
- # parenthesized expression
- 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']:
- # Expression is a array constructor
- if isinstance(items, Expr):
- items = (items,)
- return as_array(items)
- # function call/indexing
- 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))
- # Warning: this could also be Fortran indexing operation..
- return as_apply(target, *args, **kwargs)
- else:
- # Expression is a C/Python indexing operation
- # (e.g. used in .pyf files)
- assert paren == 'SQUARE'
- return target[args]
- # Fortran standard conforming identifier
- m = re.match(r'\A\w[\w\d_]*\Z', r)
- if m:
- return as_symbol(r)
- # fall-back to symbol
- r = self.finalize_string(restore(r))
- ewarn(
- f'fromstring: treating {r!r} as symbol (original={self.original})')
- return as_symbol(r)
|