PyForTool
Python-fortran-tool
Loading...
Searching...
No Matches
expressions.py
1"""
2This module includes functions to deal with expressions
3These functions are independent of the PYFT and PYFTscope objects
4"""
5
6import re
7from functools import lru_cache
8import copy
9import xml.etree.ElementTree as ET
10
11from pyfortool.util import debugDecor, isint, isfloat, fortran2xml, PYFTError
12from pyfortool import NAMESPACE
13
14
15def createElem(tagName, text=None, tail=None):
16 """
17 :param tagName: tag of the element to create
18 :param text: None or text of the element
19 :param tail: None or tail of the element
20 """
21 node = ET.Element(f'{{{NAMESPACE}}}{tagName}')
22 if text is not None:
23 node.text = text
24 if tail is not None:
25 node.tail = tail
26 return node
27
28
29@lru_cache
31 """
32 :param value: expression part to put in a *-E node
33
34 If value is:
35 - a FORTRAN string (python sting containing a ' or a "), returns
36 <f:string-E><f:S>...
37 - a FORTRAN value (python string convertible in real or int, or .FALSE./.TRUE.), returns
38 <f:literal-E><f:l>...
39 - a FORTRAN variable name (pyhon string with only alphanumerical characters and _), returns
40 <named-E/><N><n>...
41 - a FORTRAN operation (other python string), returns the right part of
42 the X affectation statement of the code:
43 "SUBROUTINE T; X=" + value + "; END". The xml is obtained by calling fxtran.
44 """
45
46 # Allowed characters in a FORTRAN variable name
47 allowed = "abcdefghijklmnopqrstuvwxyz"
48 allowed += allowed.upper() + '0123456789_'
49
50 if isint(value) or isfloat(value) or value.upper() in ('.TRUE.', '.FALSE.'):
51 node = createElem('literal-E')
52 node.append(createElem('l', text=str(value)))
53 elif "'" in value or '"' in value:
54 node = createElem('string-E')
55 node.append(createElem('S', text=value))
56 elif all(c in allowed for c in value):
57 nodeN = createElem('N')
58 nodeN.append(createElem('n', text=value))
59 node = createElem('named-E')
60 node.append(nodeN)
61 elif re.match(r'[a-zA-Z_][a-zA-Z0-9_]*%[a-zA-Z_][a-zA-Z0-9_]*$', value):
62 # A%B
63 nodeN = createElem('N')
64 nodeN.append(createElem('n', text=value.split('%')[0]))
65 ct = createElem('ct', text=value.split('%')[1])
66 componentR = createElem('component-R', text='%')
67 componentR.append(ct)
68 nodeRLT = createElem('R-LT')
69 nodeRLT.append(componentR)
70 node = createElem('named-E')
71 node.append(nodeN)
72 node.append(nodeRLT)
73 else:
74 _, xml = fortran2xml(f"SUBROUTINE T; X={value}; END")
75 node = xml.find('.//{*}E-2')[0]
76 return node
77
78
79@debugDecor
80def createExprPart(value):
81 """
82 :param value: expression part to put in a *-E node
83
84 If value is:
85 - a FORTRAN string (python sting containing a ' or a "), returns
86 <f:string-E><f:S>...
87 - a FORTRAN value (python string convertible in real or int, or .FALSE./.TRUE.), returns
88 <f:literal-E><f:l>...
89 - a FORTRAN variable name (pyhon string with only alphanumerical characters and _), returns
90 <named-E/><N><n>...
91 - a FORTRAN operation (other python string), returns the right part of
92 the X affectation statement of the code:
93 "SUBROUTINE T; X=" + value + "; END". The xml is obtained by calling fxtran.
94 """
95 return copy.deepcopy(_cachedCreateExprPart(value))
96
97
98@lru_cache
100 """
101 :param value: statements to convert into xml
102 :return: the xml fragment corresponding to value (list of nodes)
103 """
104 return fortran2xml(f"SUBROUTINE T\n{value}\nEND")[1].find('.//{*}program-unit')[1:-1]
105
106
107@debugDecor
108def createExpr(value):
109 """
110 :param value: statements to convert into xml
111 :return: the xml fragment corresponding to value (list of nodes)
112 """
113 return copy.deepcopy(_cachedCreateExpr(value))
114
115
116@debugDecor
117def simplifyExpr(expr, add=None, sub=None):
118 """
119 :param expr: string containing an expression to simplify
120 :param add: string containing an expression to add
121 :param sub: string containing an expression to substract
122 :return: simplified expression
123 E.g. simplifyExpr('1+1+I+JI-I') => '2+JI'
124 Note: only additions and substractions are considered
125 addition and subtraction within parentheses are forbidden
126 """
127 # We could have used external module, such as sympy, but this routine
128 # (as long as it's sufficient) avoids introducing dependencies.
129 if re.search(r'\‍([^()]*[+-][^()]*\‍)', expr):
130 raise NotImplementedError("Expression cannot (yet) contain + or - sign inside " +
131 f"parenthesis: {expr}")
132
133 def split(expr):
134 """
135 :param s: expression
136 :return: a list of (sign, abs(value))
137 """
138 # splt is ['1', '+', '1', '+', 'I', '+', 'JI', '-', 'I']
139 splt = re.split('([+-])', expr.replace(' ', '').upper())
140 if splt[0] == '':
141 # '-1' returns [
142 splt = splt[1:]
143 if len(splt) % 2 == 1:
144 # expr doesn't start with a sign
145 splt = ['+'] + splt # ['+', '1', '+', '1', '+', 'I', '+', 'JI', '-', 'I']
146 # group sign and operand [('+', '1'), ('+', '1'), ('+', 'I'), ('+', 'JI'), ('-', 'I')]
147 splt = [(splt[2 * i], splt[2 * i + 1]) for i in range(len(splt) // 2)]
148 return splt
149
150 splt = split(expr)
151 if add is not None:
152 splt += split(add)
153 if sub is not None:
154 splt += [('-' if sign == '+' else '+', elem) for (sign, elem) in split(sub)]
155 # Suppress elements with opposite signs
156 for sign, elem in splt.copy():
157 if ('+', elem) in splt and ('-', elem) in splt:
158 splt.remove(('+', elem))
159 splt.remove(('-', elem))
160 # Pre-compute integer additions/substractions
161 found = -1
162 for i, (sign, elem) in enumerate(splt.copy()):
163 if isint(elem):
164 if found == -1:
165 found = i
166 else:
167 result = str((1 if splt[found][0] == '+' else -1) * int(splt[found][1]) +
168 (1 if sign == '+' else -1) * int(elem))
169 splt[found] = split(str(result))[0]
170 splt.pop(i)
171 # Order (no matter what ordering is done but we need to order to allow comparisons)
172 splt.sort(key=''.join)
173 # Empty e.g. '1-1'
174 if len(splt) == 0:
175 splt = [('+', '0')]
176 # Concatenate
177 result = ' '.join(s[0] + ' ' + s[1] for s in splt)
178 if result.startswith('+'):
179 result = result[1:]
180 return result.lstrip(' ')
181
182
183@debugDecor
184def createArrayBounds(lowerBoundstr, upperBoundstr, context):
185 """
186 Return a lower-bound and upper-bound node
187 :param lowerBoundstr: string for the fortran lower bound of an array
188 :param upperBoundstr: string for the fortran upper bound of an array
189 :param context: 'DO' for DO loops
190 'DOCONCURRENT' for DO CONCURRENT loops
191 'ARRAY' for arrays
192 """
193 lowerBound = createElem('lower-bound')
194 lowerBound.insert(0, createExprPart(lowerBoundstr))
195 upperBound = createElem('upper-bound')
196 upperBound.insert(0, createExprPart(upperBoundstr))
197 if context == 'DO':
198 lowerBound.tail = ', '
199 elif context in ('DOCONCURRENT', 'ARRAY'):
200 lowerBound.tail = ':'
201 else:
202 raise PYFTError(f'Context unknown in createArrayBounds: {context}')
203 return lowerBound, upperBound