PyForTool
Python-fortran-tool
Loading...
Searching...
No Matches
statements.py
1"""
2This module includes the Statements class containing methods to act on statements
3"""
4
5import re
6import logging
7import copy
8from pyfortool.util import n2name, nonCode, debugDecor, alltext, PYFTError, tag, noParallel
9from pyfortool.expressions import createExprPart, createArrayBounds, createElem
10from pyfortool.tree import updateTree
11from pyfortool.variables import updateVarList
12from pyfortool import NAMESPACE
13
14
15def _nodesInIf(ifNode):
16 """
17 Internal method to return nodes in if structure
18 """
19 nodes = []
20 for block in ifNode.findall('./{*}if-block'):
21 for item in [i for i in block
22 if tag(i) not in ('if-then-stmt', 'else-if-stmt',
23 'else-stmt', 'end-if-stmt')]:
24 if not nonCode(item):
25 nodes.append(item)
26 return nodes
27
28
29def _nodesInWhere(whereNode):
30 """
31 Internal method to return nodes in where structure
32 """
33 nodes = []
34 for block in whereNode.findall('./{*}where-block'):
35 for item in [i for i in block
36 if tag(i) not in ('where-construct-stmt', 'else-where-stmt',
37 'end-where-stmt')]:
38 if not nonCode(item):
39 nodes.append(item)
40 return nodes
41
42
43def _nodesInDo(doNode):
44 """
45 Internal method to return nodes in do structure
46 """
47 nodes = []
48 for item in [i for i in doNode if tag(i) not in ('do-stmt', 'end-do-stmt')]:
49 if not nonCode(item):
50 nodes.append(item)
51 return nodes
52
53
54def _nodesInCase(caseNode):
55 """
56 Internal method to return nodes in do structure
57 """
58 nodes = []
59 for block in caseNode.findall('./{*}selectcase-block'):
60 for item in [i for i in block
61 if tag(i) not in ('select-case-stmt', 'case-stmt',
62 'end-select-case-stmt')]:
63 if not nonCode(item):
64 nodes.append(item)
65 return nodes
66
67
68class Statements():
69 """
70 Methods to act on statements
71 """
72
73 # No @debugDecor for this low-level method
74 def isNodeInProcedure(self, node, procList):
75 """
76 Return True if node (named-E) is an argument of a procedure listed in procList
77 :param node: node to test
78 :param procList: list of procedure names
79 """
80 # E.g. The xml for "ASSOCIATED(A)" is
81 # <f:named-E>
82 # <f:N><f:n>ASSOCIATED</f:n></f:N>
83 # <f:R-LT><f:parens-R>(
84 # <f:element-LT><f:element><f:named-E><f:N><f:n>A</f:n></f:N>
85 # </f:named-E></f:element></f:element-LT>)
86 # </f:parens-R></f:R-LT>
87 # </f:named-E>
88 inside = False
89 par = self.getParent(node)
90 if tag(par) == 'element':
91 par = self.getParent(par)
92 if tag(par) == 'element-LT':
93 par = self.getParent(par)
94 if tag(par) == 'parens-R':
95 par = self.getParent(par)
96 if tag(par) == 'R-LT':
97 previous = self.getSiblings(par, before=True, after=False)
98 if len(previous) > 0 and tag(previous[-1]) == 'N' and \
99 n2name(previous[-1]).upper() in [p.upper() for p in procList]:
100 inside = True
101 return inside
102
103 # No @debugDecor for this low-level method
104 def isNodeInCall(self, node):
105 """
106 Return True if node (named-E) is an argument of a called procedure
107 :param node: node to test
108 """
109 # E.g. The xml for "CALL FOO(A)" is
110 # <f:call-stmt>CALL
111 # <f:procedure-designator><f:named-E><f:N><f:n>FOO</f:n></f:N>
112 # </f:named-E></f:procedure-designator>(
113 # <f:arg-spec><f:arg><f:named-E><f:N><f:n>A</f:n></f:N></f:named-E></f:arg></f:arg-spec>)
114 # </f:call-stmt>
115 inside = False
116 par = self.getParent(node)
117 if tag(par) == 'arg':
118 par = self.getParent(par)
119 if tag(par) == 'arg-spec':
120 par = self.getParent(par)
121 if tag(par) == 'call-stmt':
122 inside = True
123 return inside
124
125 @debugDecor
126 def removeCall(self, callName, simplify=False):
127 """
128 :param callName: name of the subprogram calls to remove.
129 :param simplify: try to simplify code (if we delete "CALL FOO(X)" and if X not
130 used else where, we also delete it; or if the call was alone inside
131 a if-then-endif construct, the construct is also removed, and
132 variables used in the if condition are also checked...)
133 :return: number of calls suppressed
134 """
135 # Select all call-stmt and filter by name
136 callNodes = [cn for cn in self.findall('.//{*}call-stmt')
137 if n2name(cn.find('.//{*}named-E/{*}N')).upper() == callName.upper()]
138 self.removeStmtNode(callNodes, simplify, simplify)
139 return len(callNodes)
140
141 @debugDecor
142 def removePrints(self, simplify=False):
143 """
144 Removes all print statements
145 :param simplify: try to simplify code (if we delete "print*, X" and if X is not
146 used else where, we also delete it; or if the print was alone inside
147 a if-then-endif construct, the construct is also removed, and
148 variables used in the if condition are also checked...)
149 """
150 self.removeStmtNode(self.findall('.//{*}print-stmt'), simplify, simplify)
151
152 @debugDecor
153 def removeArraySyntax(self, concurrent=False, useMnhExpand=True, everywhere=True,
154 loopVar=None, reuseLoop=True, funcList=None,
155 updateMemSet=False, updateCopy=False, addAccIndependentCollapse=True):
156 """
157 Transform array syntax into DO loops
158 :param concurrent: use 'DO CONCURRENT' instead of simple 'DO' loops
159 :param useMnhExpand: use the mnh directives to transform the entire bloc in a single loop
160 :param everywhere: transform all array syntax in DO loops
161 :param loopVar: None to create new variable for each added DO loop, or
162 a function that return the name of the variable to use for the loop control.
163 This function returns a string (name of the variable), or True to create
164 a new variable, or False to not transform this statement
165 The functions takes as arguments:
166 - lower and upper bounds as defined in the declaration statement
167 - lower and upper bounds as given in the statement
168 - name of the array
169 - index of the rank
170 :param reuseLoop: if True, try to reuse loop created whith everywhere=True
171 :param funcList: list of entity names that must be recognized as array functions
172 (in addition to the intrisic ones) to discard from transformation
173 statements that make use of them. None is equivalent to an empty list.
174 :param updateMemSet: True to put affectation to constante in DO loops
175 :param updateCopy: True to put array copy in DO loops
176 :param addAccIndependentCollapse: True to add !$acc loop independent collapse(X) before
177 the DO construct
178
179 Notes: * With useMnhExpand, the function checks if the coding is conform to what is needed
180 for the filepp/mnh_expand tool (to not breack compatibility with this tool)
181 * Arrays are transformed only if ':' are used.
182 A=A(:) is not transformed at all (or raises an exception if found in a WHERE block)
183 A(:)=A is wrongly transformed into "DO...; A(J1)=A; ENDDO" and will produce a
184 compilation error
185 WHERE(L) X(:)=0. is not transformed at all (unknown behaviour in case
186 of nested WHERE)
187 * This function is not compatible with functions that return arrays:
188 X(1:5)=FUNC(1) will be transformed into "DO J1=1,5; X(J1)=FUNC(1); ENDDO"
189 x(1:5)=FUNC(X(1:5)) will be transformed into "DO J1=1,5; X(J1)=FUNC(X(J1)); ENDDO"
190 But intrinsic functions (COUNT, ANY...) are recognised and corresponding statements
191 are not transformed.
192 The list of intrinsic array functions can be extended by user functions with the
193 funcList argument.
194 """
195
196 # Developer notes:
197 # We use recursivity to avoid the use of the 'getParent' function.
198 # We start from the top node and call 'recur'.
199 #
200 # The 'recur' function loops over the different nodes and:
201 # - search for mnh directives (if 'useMnhExpand' is True):
202 # - when it is an opening directive:
203 # - decode the directive to identify bounds and variables to use
204 # ('decode' function)
205 # - introduce the DO loops (with 'createDoConstruct')
206 # - activate the 'inMnh' flag
207 # - when it is a closing directive:
208 # - deactivate the 'inMnh' flag
209 # - while the 'inMnh' flag is activated:
210 # - update ('updateStmt' function, that uses 'arrayR2parensR') and put all statements
211 # in the DO loops
212 # - in case (if 'everywhere' is True) statement is expressed using array-syntax:
213 # - find the bounds and guess a set of variables to use ('findArrayBounds' function)
214 # - introduce the DO loops (with 'createDoConstruct') if we cannot reuse the
215 # previous one
216 # - update ('updateStmt' function, that uses 'arrayR2parensR') and put all statements
217 # in the DO loops
218 # - in case the statement contains other statements (SUBROUTINE, DO loop...), call 'recur'
219 # on it
220 #
221 # Because we iterate on the statements, the tree structure cannot be modified during the
222 # iteration.
223 # All the modifications to apply are, instead, stored in objetcs ('toinsert', 'toremove'
224 # and 'varList') are applied afterwards.
225 #
226 # In addition, a number of instructions are needed to preserve and/or modify the indentation
227 # and can somewhat obfuscate the source code.
228
229 def decode(directive):
230 """
231 Decode mnh_expand directive
232 :param directive: mnh directive text
233 :return: (table, kind) where
234 table is a dictionnary: keys are variable names, values are tuples with first
235 and last index
236 kind is 'array' or 'where'
237 """
238 # E.g. !$mnh_expand_array(JIJ=IIJB:IIJE,JK=1:IKT)
239 # We expect that the indexes are declared in the same order as the one they appear
240 # in arrays
241 # For the example given, arrays are addressed with (JIJ, JK)
242 # For this example, return would be
243 # ('array', {'JIJ':('IIJB', 'IIJE'), 'JK':('1', 'IKT')})
244 table = directive.split('(')[1].split(')')[0].split(',')
245 table = {c.split('=')[0]: c.split('=')[1].split(':')
246 for c in table} # ordered since python 3.7
247 table.pop('OPENACC', None) # OPENACC='gang' in MNH v6.0 mnh_expand
248 if directive.lstrip(' ').startswith('!$mnh_expand'):
249 kind = directive[13:].lstrip(' ').split('(')[0].strip()
250 else:
251 kind = directive[17:].lstrip(' ').split('(')[0].strip()
252 return table, kind
253
254 def updateStmt(stmt, table, kind, extraindent, parent, scope):
255 """
256 Updates the statement given the table dictionnary '(:, :)' is replaced by '(JI, JK)' if
257 table.keys() is ['JI', 'JK']
258 :param stmt: statement to update
259 :param table: dictionnary retruned by the decode function
260 :param kind: kind of mnh directives: 'array' or 'where'
261 or None if transformation is not governed by
262 mnh directive
263 :param scope: current scope
264 """
265
266 def addExtra(node, extra):
267 """Helper function to add indentation spaces"""
268 if extra != 0 and (node.tail is not None) and '\n' in node.tail:
269 # We add indentation after new line only
270 # - if tail already contains a '\n' to discard
271 # a-stmt followed by a comment
272 # or '&' immediatly followed by something at the beginning of a line
273 # - if not folowed by another new line (with optional space in between)
274 node.tail = re.sub(r"(\n[ ]*)(\Z|[^\n ]+)",
275 r"\1" + extra * ' ' + r"\2", node.tail)
276
277 addExtra(stmt, extraindent) # Set indentation for the *next* node
278 if tag(stmt) == 'C':
279 pass
280 elif tag(stmt) == 'cpp':
281 i = list(parent).index(stmt)
282 if i == 0:
283 # In this case, it would be a solution to add an empty comment before the
284 # node stmt to easilty control the indentation contained in the tail
285 raise PYFTError("How is it possible?")
286 parent[i - 1].tail = parent[i - 1].tail.rstrip(' ') # Remove the indentation
287 elif tag(stmt) == 'a-stmt':
288 sss = stmt.findall('./{*}E-1/{*}named-E/{*}R-LT/{*}array-R/' +
289 '{*}section-subscript-LT/{*}section-subscript')
290 if len([ss for ss in sss if ':' in alltext(ss)]) != len(table):
291 raise PYFTError("Inside code sections to transform in DO loops, " +
292 "all affectations must use ':'.\n" +
293 "This is not the case in:\n{stmt}".format(stmt=alltext(stmt)))
294 if stmt.find('./{*}E-1/{*}named-E/{*}N').tail is not None and kind is not None:
295 raise PYFTError("To keep the compatibility with the filepp version of loop " +
296 "expansion, nothing must appear between array names and " +
297 "opening parethesis inside mnh directive sections.")
298 # We loop on named-E nodes (and not directly on array-R nodes to prevent using
299 # the costly getParent)
300 for namedE in stmt.findall('.//{*}R-LT/..'):
301 scope.arrayR2parensR(namedE, table) # Replace slices by variable
302 for cnt in stmt.findall('.//{*}cnt'):
303 addExtra(cnt, extraindent) # Add indentation after continuation characters
304 elif tag(stmt) == 'if-stmt':
305 logging.warning(
306 "An if statement is inside a code section transformed in DO loop in %s",
307 scope.getFileName())
308 # Update the statement contained in the action node
309 updateStmt(stmt.find('./{*}action-stmt')[0], table, kind, 0, stmt, scope)
310 elif tag(stmt) == 'if-construct':
311 logging.warning(
312 "An if construct is inside a code section transformed in DO loop in %s",
313 scope.getFileName())
314 # Loop over the blocks: if, elseif, else
315 for ifBlock in stmt.findall('./{*}if-block'):
316 for child in ifBlock: # Loop over each statement inside the block
317 if tag(child) not in ('if-then-stmt', 'else-if-stmt',
318 'else-stmt', 'end-if-stmt'):
319 updateStmt(child, table, kind, extraindent, ifBlock, scope)
320 else:
321 # Update indentation because the loop is here and not in recur
322 addExtra(child, extraindent)
323 for cnt in child.findall('.//{*}cnt'):
324 # Add indentation spaces after continuation characters
325 addExtra(cnt, extraindent)
326 elif tag(stmt) == 'where-stmt':
327 # Where statement becomes if statement
328 stmt.tag = f'{{{NAMESPACE}}}if-stmt'
329 stmt.text = 'IF (' + stmt.text.split('(', 1)[1]
330 # Update the action part
331 updateStmt(stmt.find('./{*}action-stmt')[0], table, kind,
332 extraindent, stmt, scope)
333 mask = stmt.find('./{*}mask-E')
334 mask.tag = f'{{{NAMESPACE}}}condition-E' # rename the condition tag
335 for namedE in mask.findall('.//{*}R-LT/..'):
336 scope.arrayR2parensR(namedE, table) # Replace slices by variable
337 for cnt in stmt.findall('.//{*}cnt'):
338 addExtra(cnt, extraindent) # Add indentation after continuation characters
339 elif tag(stmt) == 'where-construct':
340 if kind != 'where' and kind is not None:
341 raise PYFTError('To keep the compatibility with the filepp version of loop " + \
342 "expansion, no where construct must appear " + \
343 "in mnh_expand_array blocks.')
344 # Where construct becomes if construct
345 stmt.tag = f'{{{NAMESPACE}}}if-construct'
346 # Loop over the blocks (where, elsewhere)
347 for whereBlock in stmt.findall('./{*}where-block'):
348 whereBlock.tag = f'{{{NAMESPACE}}}if-block'
349 for child in whereBlock: # Loop over each statement inside the block
350 if tag(child) == 'end-where-stmt':
351 # rename ENDWHERE into ENDIF
352 child.tag = f'{{{NAMESPACE}}}end-if-stmt'
353 child.text = 'END IF'
354 # Update indentation because the loop is here and not in recur
355 addExtra(child, extraindent)
356 elif tag(child) in ('where-construct-stmt', 'else-where-stmt'):
357 # Update indentation because the loop is here and not in recur
358 addExtra(child, extraindent)
359 if tag(child) == 'where-construct-stmt':
360 # rename WHERE into IF (the THEN part is attached to the condition)
361 child.tag = f'{{{NAMESPACE}}}if-then-stmt'
362 child.text = 'IF (' + child.text.split('(', 1)[1]
363 else:
364 # In where construct the same ELSEWHERE keyword is used with or
365 # without mask. Whereas for if structure ELSEIF is used with a
366 # condition and ELSE without condition
367 if '(' in child.text:
368 # rename ELSEWHERE into ELSEIF
369 child.tag = f'{{{NAMESPACE}}}else-if-stmt'
370 child.text = 'ELSE IF (' + child.text.split('(', 1)[1]
371 else:
372 # rename ELSEWHERE into ELSE
373 child.tag = f'{{{NAMESPACE}}}else-stmt'
374 child.text = 'ELSE'
375 for mask in child.findall('./{*}mask-E'): # would a find be enough?
376 # add THEN
377 mask.tag = f'{{{NAMESPACE}}}condition-E'
378 mask.tail += ' THEN'
379 for namedE in mask.findall('.//{*}R-LT/..'):
380 # Replace slices by variable in the condition
381 scope.arrayR2parensR(namedE, table)
382 for cnt in child.findall('.//{*}cnt'):
383 # Add indentation spaces after continuation characters
384 addExtra(cnt, extraindent)
385 else:
386 updateStmt(child, table, kind, extraindent, whereBlock, scope)
387 else:
388 raise PYFTError('Unexpected tag found in mnh_expand ' +
389 'directives: {t}'.format(t=tag(stmt)))
390 return stmt
391
392 def closeLoop(loopdesc):
393 """Helper function to deal with indentation"""
394 if loopdesc:
395 inner, outer, indent, extraindent = loopdesc
396 if inner[-2].tail is not None:
397 # tail of last statement in DO loop before transformation
398 outer.tail = inner[-2].tail[:-extraindent]
399 inner[-2].tail = '\n' + (indent + extraindent - 2) * ' ' # position of the ENDDO
400 return False
401
402 toinsert = [] # list of nodes to insert
403 toremove = [] # list of nodes to remove
404 newVarList = [] # list of new variables
405
406 def recur(elem, scope):
407 inMnh = False # are we in a DO loop created by a mnh directive
408 inEverywhere = False # are we in a created DO loop (except if done with mnh directive)
409 tailSave = {} # Save tail before transformation (to retrieve original indentation)
410 for ie, sElem in enumerate(list(elem)): # we loop on elements in the natural order
411 if tag(sElem) == 'C' and sElem.text.lstrip(' ').startswith('!$mnh_expand') and \
412 useMnhExpand:
413 # This is an opening mnh directive
414 if inMnh:
415 raise PYFTError('Nested mnh_directives are not allowed')
416 inMnh = True
417 inEverywhere = closeLoop(inEverywhere) # close other loop if needed
418
419 # Directive decoding
420 table, kind = decode(sElem.text)
421 # indentation of next statement
422 indent = len(sElem.tail) - len(sElem.tail.rstrip(' '))
423 toremove.append((elem, sElem)) # we remove the directive itself
424 if ie != 0:
425 # We add, to the tail of the previous node, the tail of
426 # the directive (except one \n)
427 if elem[ie - 1].tail is None:
428 elem[ie - 1].tail = ''
429 elem[ie - 1].tail += sElem.tail.replace('\n', '', 1).rstrip(' ')
430
431 # Building acc loop collapse independent directive
432 if addAccIndependentCollapse:
433 accCollapse = createElem('C', text='!$acc loop independent collapse(' +
434 str(len(table.keys())) + ')',
435 tail='\n' + indent * ' ')
436 toinsert.append((elem, accCollapse, ie))
437
438 # Building loop
439 inner, outer, extraindent = scope.createDoConstruct(table, indent=indent,
440 concurrent=concurrent)
441 toinsert.append((elem, outer, ie)) # Place to insert the loop
442
443 elif (tag(sElem) == 'C' and
444 sElem.text.lstrip(' ').startswith('!$mnh_end_expand') and useMnhExpand):
445 # This is a closing mnh directive
446 if not inMnh:
447 raise PYFTError('End mnh_directive found before begin directive ' +
448 'in {f}'.format(f=scope.getFileName()))
449 if (table, kind) != decode(sElem.text):
450 raise PYFTError("Opening and closing mnh directives must be conform " +
451 "in {f}".format(f=scope.getFileName()))
452 inMnh = False
453 toremove.append((elem, sElem)) # we remove the directive itself
454 # We add, to the tail of outer DO loop, the tail of the
455 # directive (except one \n)
456 # pylint: disable-next=undefined-loop-variable
457 outer.tail += sElem.tail.replace('\n', '', 1) # keep all but one new line char
458 # previous item controls the position of ENDDO
459 elem[ie - 1].tail = elem[ie - 1].tail[:-2]
460
461 elif inMnh:
462 # This statement is between the opening and closing mnh directive
463 toremove.append((elem, sElem)) # we remove it from its old place
464 inner.insert(-1, sElem) # Insert first in the DO loop
465 # then update, providing new parent in argument
466 updateStmt(sElem, table, kind, extraindent, inner, scope)
467
468 elif everywhere and tag(sElem) in ('a-stmt', 'if-stmt', 'where-stmt',
469 'where-construct'):
470 # This node could contain array-syntax
471
472 # Is the node written using array-syntax? Getting the first array...
473 if tag(sElem) == 'a-stmt':
474 # Left side of the assignment
475 arr = sElem.find('./{*}E-1/{*}named-E/{*}R-LT/{*}array-R/../..')
476 # Right side
477 isMemSet = False
478 isCopy = False
479 nodeE2 = sElem.find('./{*}E-2')
480 # Number of arrays using array-syntax
481 num = len(nodeE2.findall('.//{*}array-R'))
482 if num == 0:
483 # It is an array initialisation when there is no array-syntax
484 # on the right side
485 # If array-syntax is used without explicit '(:)', it could be
486 # detected as an initialisation
487 isMemSet = True
488 elif (len(nodeE2) == 1 and tag(nodeE2[0]) == 'named-E' and num == 1 and
489 nodeE2[0].find('.//{*}parens-R') is None):
490 # It is an array copy when there is only one child in the right
491 # hand side and this child is a named-E and this child contains only
492 # one array-R node and no parens-R
493 isCopy = True
494 # Discard?
495 if (isMemSet and not updateMemSet) or (isCopy and not updateCopy):
496 arr = None
497 elif tag(sElem) == 'if-stmt':
498 # We only deal with assignment in the if statement case
499 arr = sElem.find('./{*}action-stmt/{*}a-stmt/{*}E-1/' +
500 '{*}named-E/{*}R-LT/{*}array-R/../..')
501 if arr is not None:
502 # In this case we transform the if statement into an if-construct
503 scope.changeIfStatementsInIfConstructs(singleItem=sElem)
504 recur(sElem, scope) # to transform the content of the if
505 arr = None # to do nothing more on this node
506 elif tag(sElem) == 'where-stmt':
507 arr = sElem.find('./{*}mask-E//{*}named-E/{*}R-LT/{*}array-R/../..')
508 elif tag(sElem) == 'where-construct':
509 arr = sElem.find('./{*}where-block/{*}where-construct-stmt/' +
510 '{*}mask-E//{*}named-E/{*}R-LT/{*}array-R/../..')
511
512 # Check if it is written using array-syntax and must not be excluded;
513 # then compute bounds
514 if arr is None:
515 # There is no array-syntax
516 newtable = None
517 elif len(set(alltext(a).count(':')
518 for a in sElem.findall('.//{*}R-LT/{*}array-R'))) > 1:
519 # All the elements written using array-syntax don't have the same rank
520 # (can be due to function calls, eg: "X(:)=FUNC(Y(:,:))")
521 newtable = None
522 elif len(set(['ALL', 'ANY', 'COSHAPE', 'COUNT', 'CSHIFT', 'DIMENSION',
523 'DOT_PRODUCT', 'EOSHIFT', 'LBOUND', 'LCOBOUND', 'MATMUL',
524 'MAXLOC', 'MAXVAL', 'MERGE', 'MINLOC', 'MINVAL', 'PACK',
525 'PRODUCT', 'REDUCE', 'RESHAPE', 'SHAPE', 'SIZE', 'SPREAD',
526 'SUM', 'TRANSPOSE', 'UBOUND', 'UCOBOUND', 'UNPACK'] +
527 (funcList if funcList is not None else [])
528 ).intersection(set(n2name(nodeN) for nodeN
529 in sElem.findall('.//{*}named-E/{*}N')))) > 0:
530 # At least one intrinsic array function is used
531 newtable = None
532 else:
533 # Guess a variable name
534 if arr is not None:
535 newtable, varNew = scope.findArrayBounds(arr, loopVar, newVarList)
536 for var in varNew:
537 var['new'] = True
538 if var not in newVarList:
539 newVarList.append(var)
540 else:
541 newtable = None
542
543 if newtable is None:
544 # We cannot convert the statement (not in array-syntax,
545 # excluded or no variable found to loop)
546 inEverywhere = closeLoop(inEverywhere) # close previous loop if needed
547 else:
548 # we have to transform the statement
549 if not (inEverywhere and table == newtable):
550 # No opened previous loop, or not coresponding
551 inEverywhere = closeLoop(inEverywhere) # close previous loop, if needed
552 # We must create a DO loop
553 if ie != 0 and elem[ie - 1].tail is not None:
554 # Indentation of the current node, attached to the previous sibling
555 # get tail before transformation
556 tail = tailSave.get(elem[ie - 1], elem[ie - 1].tail)
557 indent = len(tail) - len(tail.rstrip(' '))
558 else:
559 indent = 0
560 table = newtable # save the information on the newly build loop
561 kind = None # not built from mnh directives
562 # Building loop
563 inner, outer, extraindent = scope.createDoConstruct(
564 table, indent=indent, concurrent=concurrent)
565 toinsert.append((elem, outer, ie)) # place to insert the loop
566 inEverywhere = (inner, outer, indent, extraindent) # we are now in loop
567 tailSave[sElem] = sElem.tail # save tail for future indentation computation
568 toremove.append((elem, sElem)) # we remove it from its old place
569 inner.insert(-1, sElem) # Insert first in the DO loop
570 # then update, providing new parent in argument
571 updateStmt(sElem, table, kind, extraindent, inner, scope)
572 if not reuseLoop:
573 # Prevent from reusing this DO loop
574 inEverywhere = closeLoop(inEverywhere)
575
576 else:
577 inEverywhere = closeLoop(inEverywhere) # close loop if needed
578 if len(sElem) >= 1:
579 # Iteration
580 recur(sElem, scope)
581 inEverywhere = closeLoop(inEverywhere)
582
583 for scope in self.getScopes():
584 recur(scope, scope)
585 # First, element insertion by reverse order (in order to keep the insertion index correct)
586 for elem, outer, ie in toinsert[::-1]:
587 elem.insert(ie, outer)
588 # Then, suppression
589 for parent, elem in toremove:
590 parent.remove(elem)
591 # And variable creation
592 self.addVar([(v['scopePath'], v['n'], f"INTEGER :: {v['n']}", None)
593 for v in newVarList])
594
595 @debugDecor
596 @noParallel
597 @updateTree('signal')
598 @updateVarList
599 def inlineContainedSubroutines(self, simplify=False, loopVar=None):
600 """
601 Inline all contained subroutines in the main subroutine
602 Steps :
603 - Identify contained subroutines
604 - Look for all CALL statements, check if it is a containted routines; if yes, inline
605 - Delete the containted routines
606 :param simplify: try to simplify code (construct or variables becoming useless)
607 :param loopVar: None to create new variable for each added DO loop (around ELEMENTAL
608 subroutine calls)
609 or a function that return the name of the variable to use for the
610 loop control.
611 This function returns a string (name of the variable), or True to create
612 a new variable, or False to not transform this statement
613 The functions takes as arguments:
614 - lower and upper bounds as defined in the declaration statement
615 - lower and upper bounds as given in the statement
616 - name of the array
617 - index of the rank
618 """
619
620 scopes = self.getScopes()
621
622 # Inline contained subroutines : look for sub: / sub:
623 containedRoutines = {}
624 for scope in scopes:
625 if scope.path.count('sub:') >= 2:
626 containedRoutines[alltext(scope.find('.//{*}subroutine-N/{*}N/{*}n'))] = scope
627 # Start by nested contained subroutines call, and end up with the last index = the main
628 # subroutine to treat
629 scopes.reverse()
630 # Loop on all subroutines (main + contained)
631 for scope in [scope for scope in scopes if scope.path.count('sub:') >= 1]:
632 # Loop on all CALL statements
633 for callStmtNn in scope.findall('.//{*}call-stmt/{*}procedure-designator/' +
634 '{*}named-E/{*}N/{*}n'):
635 for containedRoutine in [cr for cr in containedRoutines
636 if alltext(callStmtNn) == cr]:
637 # name of the routine called = a contained subroutine => inline
638 self.inline(containedRoutines[containedRoutine],
639 self.getParent(callStmtNn, level=4),
640 scope,
641 simplify=simplify, loopVar=loopVar)
642
643 for scope in scopes: # loop on all subroutines
644 if scope.path.count('sub:') >= 2:
645 # This is a contained subroutine
646 name = scope.path.split(':')[-1].upper() # Subroutine name
647 # All nodes refering the subroutine
648 nodes = [nodeN for nodeN in self.findall('.//{*}N')
649 if n2name(nodeN).upper() == name]
650 if all(nodeN in scope.iter() for nodeN in nodes):
651 # Subroutine name not used (apart in its definition scope),
652 # we suppress it from the CONTAINS part
653 self.remove(scope)
654 self.tree.signal(self) # Tree must be updated, only in this case
655
656 if simplify:
657 self.removeEmptyCONTAINS()
658
659 @debugDecor
660 @noParallel
661 @updateTree()
662 @updateVarList
663 def inline(self, subContained, callStmt, mainScope,
664 simplify=False, loopVar=None):
665 """
666 Inline a subContainted subroutine
667 Steps :
668 - update the main code if needed (if statement and/or elemental)
669 - copy the subContained node
670 - remove everything before the declarations variables and the variables declarations
671 - deal with optional argument
672 - from the callStmt, replace all the arguments by their names
673 - inline in the main code
674 - add local variables and use statements to the main code
675 :param subContained: xml fragment corresponding to the sub: to inline
676 :param callStmt: the call-stmt to replace
677 :param mainScope: scope of the main (calling) subroutine
678 :param simplify: try to simplify code (construct or variables becoming useless)
679 :param loopVar: None to create new variable for each added DO loop (around ELEMENTAL
680 subroutine calls)
681 or a function that return the name of the variable to use for the
682 loop control.
683 This function returns a string (name of the variable), or True to create
684 a new variable, or False to not transform this statement
685 The functions takes as arguments:
686 - lower and upper bounds as defined in the declaration statement
687 - lower and upper bounds as given in the statement
688 - name of the array
689 - index of the rank
690 """
691 def setPRESENTby(node, var, val):
692 """
693 Replace PRESENT(var) by .TRUE. if val is True, by .FALSE. otherwise on node
694 if var is found
695 :param node: xml node to work on (a contained subroutine)
696 :param var: string of the name of the optional variable to check
697 """
698 for namedE in node.findall('.//{*}named-E/{*}N/..'):
699 if n2name(namedE.find('./{*}N')).upper() == 'PRESENT':
700 presentarg = n2name(namedE.find('./{*}R-LT/{*}parens-R/{*}element-LT/'
701 '{*}element/{*}named-E/{*}N'))
702 if presentarg.upper() == var.upper():
703 for nnn in namedE[:]:
704 namedE.remove(nnn)
705 namedE.tag = f'{{{NAMESPACE}}}literal-E'
706 namedE.text = '.TRUE.' if val else '.FALSE.'
707
708 # Get parent of callStmt
709 parent = mainScope.getParent(callStmt)
710
711 # Expand the if-construct if the call-stmt is in a one-line if-construct
712 if tag(parent) == 'action-stmt':
713 mainScope.changeIfStatementsInIfConstructs(mainScope.getParent(parent))
714 parent = mainScope.getParent(callStmt) # update parent
715
716 # Specific case for ELEMENTAL subroutines
717 # Introduce DO-loops if it is called on arrays
718 prefix = subContained.findall('.//{*}prefix')
719 if len(prefix) > 0 and 'ELEMENTAL' in [p.text.upper() for p in prefix]:
720 # Add missing parentheses
721 mainScope.addArrayParenthesesInNode(callStmt)
722
723 # Add explcit bounds
724 mainScope.addExplicitArrayBounds(node=callStmt)
725
726 # Detect if subroutine is called on arrays
727 arrayRincallStmt = callStmt.findall('.//{*}array-R')
728 if len(arrayRincallStmt) > 0: # Called on arrays
729 # Look for an array affectation to guess the DO loops to put around the call
730 table, _ = mainScope.findArrayBounds(mainScope.getParent(arrayRincallStmt[0], 2),
731 loopVar)
732
733 # Add declaration of loop index if missing
734 for varName in table.keys():
735 if not mainScope.varList.findVar(varName):
736 var = {'as': [], 'asx': [],
737 'n': varName, 'i': None, 't': 'INTEGER', 'arg': False,
738 'use': False, 'opt': False, 'allocatable': False,
739 'parameter': False, 'init': None, 'scopePath': mainScope.path}
740 mainScope.addVar([[mainScope.path, var['n'],
741 mainScope.varSpec2stmt(var), None]])
742
743 # Create the DO loops
744 inner, outer, _ = mainScope.createDoConstruct(table)
745
746 # Move the call statement in the DO loops
747 inner.insert(-1, callStmt) # callStmt in the DO-loops
748 # DO-loops near the original call stmt
749 parent.insert(list(parent).index(callStmt), outer)
750 parent.remove(callStmt) # original call stmt removed
751 parent = inner # Update parent
752 for namedE in callStmt.findall('./{*}arg-spec/{*}arg/{*}named-E'):
753 # Replace slices by indexes if any
754 if namedE.find('./{*}R-LT'):
755 mainScope.arrayR2parensR(namedE, table)
756
757 # Deep copy the object to possibly modify the original one multiple times
758 node = copy.deepcopy(subContained)
759
760 # Get local variables that are not present in the main routine for later addition
761 localVarToAdd = []
762 subst = []
763 varList = copy.deepcopy(self.varList) # Copy to be able to update it with pending changes
764 for var in [var for var in varList.restrict(subContained.path, True)
765 if not var['arg'] and not var['use']]:
766
767 if varList.restrict(mainScope.path, True).findVar(var['n']):
768 # Variable is already defined in main or upper, there is a name conflict,
769 # the local variable must be renamed before being declared in the main routine
770 newName = re.sub(r'_\d+$', '', var['n'])
771 i = 1
772 while (varList.restrict(subContained.path, True).findVar(newName + '_' + str(i)) or
773 varList.restrict(mainScope.path, True).findVar(newName + '_' + str(i))):
774 i += 1
775 newName += '_' + str(i)
776 node.renameVar(var['n'], newName)
777 subst.append((var['n'], newName))
778 var['n'] = newName
779 # important for varList.findVar(.., mainScope.path) to find it
780 var['scopePath'] = mainScope.path
781 localVarToAdd.append(var)
782
783 # In case a substituted variable is used in the declaration of another variable
784 for oldName, newName in subst:
785 for var in localVarToAdd + varList[:]:
786 if var['as'] is not None:
787 var['as'] = [[re.sub(r'\b' + oldName + r'\b', newName, dim[i])
788 if dim[i] is not None else None
789 for i in (0, 1)]
790 for dim in var['as']]
791
792 # Remove all objects that is implicit none, comment or else until reach
793 # something interesting
794 # USE statements are stored for later user
795 # subroutine-stmt and end-subroutine-stmt are kept to ensure consistency
796 # (for removeStmtNode with simplify)
797 localUseToAdd = node.findall('./{*}use-stmt')
798 for sNode in node.findall('./{*}T-decl-stmt') + localUseToAdd + \
799 node.findall('./{*}implicit-none-stmt'):
800 node.remove(sNode)
801 while tag(node[1]) == 'C' and not node[1].text.startswith('!$acc'):
802 node.remove(node[1])
803
804 # Variable correspondance
805 # For each dummy argument, we look for the calling arg name and shape
806 # CALL FOO(Z(:))
807 # SUBROUTINE FOO(P)
808 # variable = {'P':{'name': 'Z', dim=[':']}}
809 vartable = {} # ordered dict
810 for argN in subContained.findall('.//{*}subroutine-stmt/{*}dummy-arg-LT/{*}arg-N'):
811 vartable[alltext(argN).upper()] = None # Not present by default
812 for iarg, arg in enumerate(callStmt.findall('.//{*}arg')):
813 key = arg.find('.//{*}arg-N')
814 if key is not None:
815 # arg is VAR=value
816 dummyName = alltext(key).upper()
817 argnode = arg[1]
818 else:
819 dummyName = list(vartable.keys())[iarg]
820 argnode = arg[0]
821 nodeRLTarray = argnode.findall('.//{*}R-LT/{*}array-R')
822 if len(nodeRLTarray) > 0:
823 # array
824 if len(nodeRLTarray) > 1 or \
825 argnode.find('./{*}R-LT/{*}array-R') is None or \
826 tag(argnode) != 'named-E':
827 # Only simple cases are treated
828 raise PYFTError('Argument to complicated: ' + str(alltext(argnode)))
829 dim = nodeRLTarray[0].find('./{*}section-subscript-LT')[:]
830 else:
831 dim = None
832 if dim is None:
833 # A%B => argname = 'A%B'
834 # Z(1) => argname = 'Z(1)'
835 argname = "".join(argnode.itertext())
836 else:
837 # Z(:) => argname = 'Z'
838 tmp = copy.deepcopy(argnode)
839 nodeRLT = tmp.find('./{*}R-LT')
840 nodeRLT.remove(nodeRLT.find('./{*}array-R'))
841 argname = "".join(tmp.itertext())
842 vartable[dummyName] = {'node': argnode, 'name': argname, 'dim': dim}
843
844 # Look for PRESENT(var) and replace it by True when variable is present, by False otherwise
845 for dummyName in [dummyName for (dummyName, value) in vartable.items()
846 if value is not None]:
847 setPRESENTby(node, dummyName, True)
848 for dummyName in [dummyName for (dummyName, value) in vartable.items()
849 if value is None]:
850 setPRESENTby(node, dummyName, False)
851
852 # Look for usage of variable not present and delete corresponding code
853 for dummyName in [dummyName for (dummyName, value) in vartable.items() if value is None]:
854 for nodeN in [nodeN for nodeN in node.findall('.//{*}named-E/{*}N')
855 if n2name(nodeN).upper() == dummyName]:
856 removed = False
857 # Parent is the named-E node, we need at least the upper level
858 par = node.getParent(nodeN, level=2)
859 allreadySuppressed = []
860 while par and not removed and par not in allreadySuppressed:
861 toSuppress = None
862 tagName = tag(par)
863 if tagName in ('a-stmt', 'print-stmt'):
864 # Context 1: an a-stmt of type E1 = E2
865 toSuppress = par
866 elif tagName == 'call-stmt':
867 # We should rewrite the call statement without this optional argument
868 # But it is not easy: we must check that the argument is really optional
869 # for the called routine and we must add (if not already present)
870 # keywords for following arguments
871 raise NotImplementedError('call-stmt not (yet?) implemented')
872 elif tagName in ('if-stmt', 'where-stmt'):
873 # Context 2: an if-stmt of type : IF(variable) ...
874 toSuppress = par
875 elif tagName in ('if-then-stmt', 'else-if-stmt', 'where-construct-stmt',
876 'else-where-stmt'):
877 # Context 3: an if-block of type : IF(variable) THEN...
878 # We delete the entire construct
879 toSuppress = node.getParent(par, 2)
880 elif tagName in ('select-case-stmt', 'case-stmt'):
881 # Context 4: SELECT CASE (variable)... or CASE (variable)
882 # We deleted the entire construct
883 toSuppress = node.getParent(par, 2)
884 elif tagName.endswith('-block') or tagName.endswith('-stmt') or \
885 tagName.endswith('-construct'):
886 # action-stmt, do-stmt, forall-construct-stmt, forall-stmt,
887 # if-block, where-block, selectcase-block,
888 # must not contain directly
889 # the variable but must contain other statements using the variable.
890 # We target the inner statement in the previous cases.
891 # Some cases may have been overlooked and should be added above.
892 raise PYFTError(("We shouldn't be here. A case may have been " +
893 "overlooked (tag={tag}).".format(tag=tagName)))
894 if toSuppress is not None:
895 removed = True
896 if toSuppress not in allreadySuppressed:
897 # We do not simplify variables to prevent side-effect with
898 # the variable renaming
899 node.removeStmtNode(toSuppress, False, simplify)
900 allreadySuppressed.extend(list(toSuppress.iter()))
901 else:
902 par = node.getParent(par)
903
904 # Loop on the dummy argument
905 for name, dummy in vartable.items():
906 # Loop on all variables in the contained routine
907 # It is important to build again the list of nodes, because it may have
908 # changed during the previous dummy argument substitution
909 for namedE in [namedE for namedE in node.findall('.//{*}named-E/{*}N/{*}n/../..')
910 if n2name(namedE.find('{*}N')).upper() == name]:
911 # 0 Concatenation of n nodes (a name could be split over several n nodes)
912 nodeN = namedE.find('./{*}N')
913 ns = nodeN.findall('./{*}n')
914 ns[0].text = n2name(nodeN)
915 for nnn in ns[1:]:
916 nodeN.remove(nnn)
917
918 # 1 We get info about variables (such as declared in the main or in
919 # the contained routines)
920 descMain = varList.restrict(mainScope.path, True).findVar(dummy['name'])
921 descSub = varList.restrict(subContained.path, True).findVar(name)
922 # In case variable is used for the declaration of another variable,
923 # we must update descSub
924 for var in varList:
925 if var['as'] is not None:
926 var['as'] = [[re.sub(r'\b' + name + r'\b', dummy['name'], dim[i])
927 if dim[i] is not None else None
928 for i in (0, 1)]
929 for dim in var['as']]
930
931 # 3 We select the indexes (only for array argument and not structure argument
932 # containing an array)
933 # using the occurrence to replace inside the subcontained routine body
934 nodeRLT = namedE.find('./{*}R-LT')
935 if nodeRLT is not None and tag(nodeRLT[0]) != 'component-R':
936 # The variable name is immediately followed by a parenthesis
937 assert tag(nodeRLT[0]) in ('array-R', 'parens-R'), 'Internal error'
938 slices = nodeRLT[0].findall('./{*}section-subscript-LT/' +
939 '{*}section-subscript')
940 slices += nodeRLT[0].findall('./{*}element-LT/{*}element')
941 else:
942 # No parenthesis
943 if (descMain is not None and len(descMain['as']) > 0) or \
944 len(descSub['as']) > 0 or dummy['dim'] is not None:
945 # No parenthesis, but this is an array, we add as many ':' as needed
946 if len(descSub['as']) > 0:
947 ndim = len(descSub['as'])
948 else:
949 # ELEMENTAL routine, dummy arg is scalar
950 if dummy['dim'] is not None:
951 # We use the variable passed as argument because
952 # parenthesis were used
953 ndim = len([d for d in dummy['dim'] if ':' in alltext(d)])
954 else:
955 # We use the declared version in main
956 ndim = len(descMain['as'])
957 ns[0].text += '(' + (', '.join([':'] * ndim)) + ')'
958 updatedNamedE = createExprPart(alltext(namedE))
959 namedE.tag = updatedNamedE.tag
960 namedE.text = updatedNamedE.text
961 for nnn in namedE[:]:
962 namedE.remove(nnn)
963 namedE.extend(updatedNamedE[:])
964 slices = namedE.find('./{*}R-LT')[0].findall(
965 './{*}section-subscript-LT/{*}section-subscript')
966 else:
967 # This is not an array
968 slices = []
969
970 # 4 New name (the resultig xml is not necessarily a valid fxtran xml)
971 namedE.find('./{*}N')[0].text = dummy['name']
972
973 # 5 We update the indexes to take into account a different declaration
974 # (lower bound especially)
975 # in the main and in the contained routines.
976 # Moreover, we could need to add indexes
977 if len(slices) > 0:
978 # This is an array
979 for isl, sl in enumerate(slices):
980 # 0 Compute bounds for array
981 if len(descSub['as']) == 0 or descSub['as'][isl][1] is None:
982 # ELEMENTAL or array with implicit shape
983 for i in (0, 1): # 0 for lower bound and 1 for upper bound
984 if dummy['dim'] is not None:
985 # Parenthesis in the call statement
986 tagName = './{*}lower-bound' if i == 0 else './{*}upper-bound'
987 descSub[i] = dummy['dim'][isl].find(tagName)
988 else:
989 descSub[i] = None
990 if descSub[i] is not None:
991 # lower/upper limit was given in the call statement
992 descSub[i] = alltext(descSub[i])
993 else:
994 # if available we take lower/upper limit set in the declaration
995 if descMain is not None and descMain['as'][isl][1] is not None:
996 # Declaration found in main, and not using implicit shape
997 descSub[i] = descMain['as'][isl][i]
998 if i == 0 and descSub[i] is None:
999 descSub[i] = '1' # Default FORTRAN value
1000 else:
1001 descSub[i] = "L" if i == 0 else "U"
1002 descSub[i] += "BOUND({name}, {isl})".format(
1003 name=dummy['name'], isl=isl + 1)
1004 else:
1005 descSub[0] = descSub['as'][isl][0]
1006 if descSub[0] is None:
1007 descSub[0] = '1' # Default FORTRAN value
1008 descSub[1] = descSub['as'][isl][1]
1009
1010 # 1 Offset computation
1011 # if only a subset is passed
1012 # and/or if lower bound of array is different in main and in sub
1013 # contained routine
1014 # REAL, DIMENSION(M1:M2):: Z; CALL FOO(N1:N2)
1015 # SUBROUTINE FOO(P); REAL, DIMENSION(K1:K2):: P; P(I1:I2)
1016 # If M1 or K1 is not set, they defaults to 1; if not set, N1 defaults to
1017 # M1 and I1 to K1
1018 # P(I1:I2)=Z(I1-K1+N1:I2-K1+N1)
1019 offset = 0
1020 if dummy['dim'] is not None and \
1021 not alltext(dummy['dim'][isl]).strip().startswith(':'):
1022 offset = alltext(dummy['dim'][isl].find('./{*}lower-bound'))
1023 else:
1024 if descMain is not None:
1025 offset = descMain['as'][isl][0]
1026 if offset is None:
1027 offset = '1' # Default FORTRAN value
1028 elif offset.strip().startswith('-'):
1029 offset = '(' + offset + ')'
1030 else:
1031 offset = "LBOUND({name}, {isl})".format(
1032 name=dummy['name'], isl=isl + 1)
1033 if offset.upper() == descSub[0].upper():
1034 offset = 0
1035 else:
1036 if descSub[0].strip().startswith('-'):
1037 offset += '- (' + descSub[0] + ')'
1038 else:
1039 offset += '-' + descSub[0]
1040
1041 # 2 Update index with the offset and add indexes instead of ':'
1042 if offset != 0:
1043 if tag(sl) == 'element' or \
1044 (tag(sl) == 'section-subscript' and ':' not in alltext(sl)):
1045 # Z(I) or last index of Z(:, I)
1046 bounds = sl
1047 else:
1048 low = sl.find('./{*}lower-bound')
1049 if low is None:
1050 low = createElem('lower-bound', tail=sl.text)
1051 low.append(createExprPart(descSub[0]))
1052 sl.text = None
1053 sl.insert(0, low)
1054 up = sl.find('./{*}upper-bound')
1055 if up is None:
1056 up = createElem('upper-bound')
1057 up.append(createExprPart(descSub[1]))
1058 sl.append(up)
1059 bounds = [low, up]
1060 for bound in bounds:
1061 # bound[-1] is a named-E, literal-E, op-E...
1062 if bound[-1].tail is None:
1063 bound[-1].tail = ''
1064 bound[-1].tail += '+' + offset # Not valid fxtran xml
1065
1066 # We must add extra indexes
1067 # CALL FOO(Z(:,1))
1068 # SUBROUTINE FOO(P); REAL, DIMENSION(K1:K2):: P
1069 # P(I) => Z(I, 1)
1070 if dummy['dim'] is not None and len(dummy['dim']) > len(slices):
1071 slices[-1].tail = ', '
1072 par = node.getParent(slices[-1])
1073 par.extend(dummy['dim'][len(slices):])
1074
1075 # 6 Convert (wrong) xml into text and into xml again (to obtain a valid fxtran xml)
1076 # This double conversion is not sufficient in some case.
1077 # E.g. variable (N/n tag) replaced by real value
1078 updatedNamedE = createExprPart(alltext(namedE))
1079 namedE.tag = updatedNamedE.tag
1080 namedE.text = updatedNamedE.text
1081 for nnn in namedE[:]:
1082 namedE.remove(nnn)
1083 namedE.extend(updatedNamedE[:])
1084
1085 node.remove(node.find('./{*}subroutine-stmt'))
1086 node.remove(node.find('./{*}end-subroutine-stmt'))
1087
1088 # Add local var and use to main routine
1089 mainScope.addVar([[mainScope.path, var['n'], mainScope.varSpec2stmt(var), None]
1090 for var in localVarToAdd])
1091 mainScope.addModuleVar([[mainScope.path, n2name(useStmt.find('.//{*}module-N//{*}N')),
1092 [n2name(v.find('.//{*}N'))
1093 for v in useStmt.findall('.//{*}use-N')]]
1094 for useStmt in localUseToAdd])
1095
1096 # Remove call statement of the contained routines
1097 index = list(parent).index(callStmt)
1098 parent.remove(callStmt)
1099 if callStmt.tail is not None:
1100 if node[-1].tail is None:
1101 node[-1].tail = callStmt.tail
1102 else:
1103 node[-1].tail = node[-1].tail + callStmt.tail
1104 for node in node[::-1]:
1105 # node is a program-unit, we must insert the subelements
1106 parent.insert(index, node)
1107
1108 @debugDecor
1109 def setFalseIfStmt(self, flags, simplify=False):
1110 """
1111 Set to .FALSE. a given boolean fortran flag before removing the node if simplify is True
1112 :param flags: list of strings of flags to set to .FALSE.
1113 :param simplify: try to simplify code (if the .FALSE. was alone inside a if-then-endif
1114 construct, the construct is removed, and variables used in the if
1115 condition are also checked)
1116 """
1117 if isinstance(flags, str):
1118 flags = [flags]
1119 flags = [flag.upper() for flag in flags]
1120 for scope in self.getScopes(excludeKinds=['type']):
1121 singleFalseBlock, multipleFalseBlock = [], []
1122 # Loop on nodes composing the scope
1123 for node in scope:
1124 # Loop on condition nodes
1125 for cond in node.findall('.//{*}condition-E'):
1126 found = False
1127 for namedE in [namedE for namedE
1128 in node.findall('.//{*}condition-E//{*}named-E')
1129 if alltext(namedE).upper() in flags]:
1130 # This named-E must be replaced by .FALSE.
1131 found = True
1132 namedE.tag = '{{{NAMESPACE}}}literal-E'
1133 namedE.text = '.FALSE.'
1134 for item in list(namedE):
1135 namedE.remove(item)
1136 if found:
1137 nodeOpE = cond.find('./{*}op-E')
1138 if nodeOpE is not None:
1139 # Multiple flags conditions
1140 multipleFalseBlock.append(nodeOpE)
1141 else:
1142 # Solo condition
1143 # <if-block><if-then-stmt>
1144 if tag(scope.getParent(cond)).startswith('if-stmt'):
1145 scope.changeIfStatementsInIfConstructs(scope.getParent(cond))
1146 singleFalseBlock.append(scope.getParent(cond, level=2))
1147 if simplify:
1148 scope.removeStmtNode(singleFalseBlock, simplify, simplify)
1149 scope.evalFalseIfStmt(multipleFalseBlock, simplify)
1150
1151 @debugDecor
1152 def evalFalseIfStmt(self, nodes, simplify=False):
1153 """
1154 Evaluate if-stmt with multiple op-E and remove the nodes if only .FALSE. are present
1155 :param nodes: list of nodes of type op-E to evaluate (containing .FALSE.)
1156 :param simplify: try to simplify code (if if-block is removed, variables used in the
1157 if condition are also checked)
1158 """
1159 nodesTorm = []
1160 for node in nodes:
1161 toRemove = True
1162 for nnn in node:
1163 if tag(nnn) == 'op' or (tag(nnn) == 'literal-E' and '.FALSE.' in alltext(nnn)):
1164 pass
1165 else:
1166 toRemove = False
1167 break
1168 if toRemove:
1169 # <if-block><if-then-stmt><condition-E>
1170 nodesTorm.append(self.getParent(level=3))
1171 self.removeStmtNode(nodesTorm, simplify, simplify)
1172
1173 @debugDecor
1174 def checkOpInCall(self, mustRaise=False):
1175 """
1176 :param mustRaise: True to raise
1177 Issue a logging.warning if some call arguments are operations
1178 If mustRaise is True, issue a logging.error instead and raise an error
1179 """
1180 ok = True
1181 log = logging.error if mustRaise else logging.warning
1182 for arg in self.findall('.//{*}call-stmt/{*}arg-spec/{*}arg/{*}op-E'):
1183 log(("The call argument {} is an operation, in file '{}'"
1184 ).format(alltext(arg).replace('\n', ' \\n '), self.getFileName()))
1185 ok = False
1186 if not ok and mustRaise:
1187 raise PYFTError(("There are call arguments which are operations in file '{}'"
1188 ).format(self.getFileName()))
1189
1190 @debugDecor
1191 def checkEmptyParensInCall(self, mustRaise=False):
1192 """
1193 :param mustRaise: True to raise
1194 Issue a logging.warning if some call arguments are arrays with empty parens
1195 Example: CALL FOO(A(:))
1196 If mustRaise is True, issue a logging.error instead and raise an error
1197 """
1198 ok = True
1199 log = logging.error if mustRaise else logging.warning
1200 for sslt in self.findall('.//{*}call-stmt/{*}arg-spec/{*}arg/{*}named-E/' +
1201 '{*}R-LT/{*}array-R/{*}section-subscript-LT'):
1202 if all(alltext(ss) == ':' for ss in sslt.findall('./{*}section-subscript')):
1203 arg = self.getParent(sslt, 3)
1204 log(("The call argument {} is an array with empty parens, in file '{}'"
1205 ).format(alltext(arg).replace('\n', ' \\n '), self.getFileName()))
1206 ok = False
1207 if not ok and mustRaise:
1208 raise PYFTError(("There are call arguments which are arrays " +
1209 "with empty parens in file '{}'").format(self.getFileName()))
1210
1211 @debugDecor
1212 def insertStatement(self, stmt, first):
1213 """
1214 Insert a statement to be executed first (or last)
1215 :param stmt: statement to insert
1216 :param first: True to insert it in first position, False to insert it in last position
1217 :return: the index of the stmt inserted in scope
1218 """
1219 # pylint: disable=unsubscriptable-object
1220 if first:
1221 # Statement must be inserted after all use, T-decl, implicit-non-stmt,
1222 # interface and cray pointers
1223 nodes = self.findall('./{*}T-decl-stmt') + self.findall('./{*}use-stmt') + \
1224 self.findall('./{*}implicit-none-stmt') + \
1225 self.findall('./{*}interface-construct') + \
1226 self.findall('./{*}pointer-stmt')
1227 if len(nodes) > 0:
1228 # Insertion after the last node
1229 index = max([list(self).index(n) for n in nodes]) + 1
1230 else:
1231 # Insertion after the subroutine or function node
1232 index = 2
1233 # If an include statements follows, it certainly contains an interface
1234 while (tag(self[index]) in ('C', 'include', 'include-stmt') or
1235 (tag(self[index]) == 'cpp' and self[index].text.startswith('#include'))):
1236 if not self[index].text.startswith('!$acc'):
1237 index += 1
1238 else:
1239 break
1240 else:
1241 # Statement must be inserted before the contains statement
1242 contains = self.find('./{*}contains-stmt')
1243 if contains is not None:
1244 # Insertion before the contains statement
1245 index = list(self).index(contains)
1246 else:
1247 # Insertion before the end subroutine or function statement
1248 index = -1
1249 if self[index - 1].tail is None:
1250 self[index - 1].tail = '\n'
1251 elif '\n' not in self[index - 1].tail:
1252 self[index - 1].tail += '\n'
1253 self.insert(index, stmt)
1254 return index
1255
1256 @debugDecor
1257 def removeStmtNode(self, nodes, simplifyVar, simplifyStruct):
1258 """
1259 This function removes a statement node and:
1260 - suppress variable that became useless (if simplifyVar is True)
1261 - suppress outer loop/if if useless (if simplifyStruct is True)
1262 :param nodes: node (or list of nodes) to remove
1263 :param simplifyVar: try to simplify code (if we delete "CALL FOO(X)" and if X not used
1264 else where, we also delete it; or if the call was alone inside a
1265 if-then-endif construct, with simplifyStruct=True, the construct is also
1266 removed, and variables used in the if condition are also checked...)
1267 :param simplifyStruct: try to simplify code (if we delete "CALL FOO(X)" and if the call was
1268 alone inside a if-then-endif construct, the construct is also
1269 removed and variables used in the if condition
1270 (with simplifyVar=True) are also checked...)
1271 """
1272
1273 # In case the suppression of an if-stmt or where-stmt is asked,
1274 # we must start by the inner statement
1275 nodesToSuppress = []
1276 if not isinstance(nodes, list):
1277 nodes = [nodes]
1278 for node in nodes:
1279 if tag(node) in ('if-stmt', 'where-stmt'):
1280 action = node.find('./{*}action-stmt')
1281 if action is not None and len(action) != 0:
1282 nodesToSuppress.append(action[0])
1283 else:
1284 nodesToSuppress.append(node)
1285 elif tag(node) == '}action-stmt':
1286 if len(node) != 0:
1287 nodesToSuppress.append(node[0])
1288 else:
1289 nodesToSuppress.append(node)
1290 else:
1291 nodesToSuppress.append(node)
1292
1293 varToCheck = [] # List of variables to check for suppression
1294 if simplifyVar:
1295 # Loop to identify all the potential variables to remove
1296 for node in nodesToSuppress:
1297 scopePath = self.getScopePath(node)
1298 if tag(node) == 'do-construct':
1299 # Try to remove variables used in the loop
1300 varToCheck.extend([(scopePath, n2name(arg))
1301 for arg in node.find('./{*}do-stmt').findall('.//{*}N')])
1302 elif tag(node) in ('if-construct', 'if-stmt'):
1303 # Try to remove variables used in the conditions
1304 varToCheck.extend([(scopePath, n2name(arg))
1305 for arg in node.findall('.//{*}condition-E//{*}N')])
1306 elif tag(node) in ('where-construct', 'where-stmt'):
1307 # Try to remove variables used in the conditions
1308 varToCheck.extend([(scopePath, n2name(arg))
1309 for arg in node.findall('.//{*}mask-E//{*}N')])
1310 elif tag(node) == 'call-stmt':
1311 # We must check if we can suppress the variables used to call the subprogram
1312 varToCheck.extend([(scopePath, n2name(arg))
1313 for arg in node.findall('./{*}arg-spec//{*}N')])
1314 # And maybe, the subprogram comes from a module
1315 varToCheck.append((scopePath,
1316 n2name(node.find('./{*}procedure-designator//{*}N'))))
1317 elif tag(node) in ('a-stmt', 'print-stmt'):
1318 varToCheck.extend([(scopePath, n2name(arg)) for arg in node.findall('.//{*}N')])
1319 elif tag(node) == 'selectcase-construct':
1320 # Try to remove variables used in the selector and in conditions
1321 varToCheck.extend([(scopePath, n2name(arg))
1322 for arg in node.findall('.//{*}case-E//{*}N')])
1323 varToCheck.extend([(scopePath, n2name(arg))
1324 for arg in node.findall('.//{*}case-value//{*}N')])
1325
1326 # Node suppression
1327 parents = {} # cache
1328 for node in nodesToSuppress:
1329 parent = self.getParent(node)
1330 parents[id(node)] = parent
1331 newlines = '\n' * (alltext(node).count('\n') if tag(node).endswith('-construct') else 0)
1332 if node.tail is not None or len(newlines) > 0:
1333 previous = self.getSiblings(node, after=False)
1334 if len(previous) == 0:
1335 previous = parent
1336 else:
1337 previous = previous[-1]
1338 if previous.tail is None:
1339 previous.tail = ''
1340 previous.tail = (previous.tail.replace('\n', '') +
1341 (node.tail if node.tail is not None else ''))
1342 parent.remove(node)
1343
1344 # Variable simplification
1345 self.removeVarIfUnused(varToCheck, excludeDummy=True,
1346 excludeModule=True, simplify=simplifyVar)
1347
1348 # List the new nodes to suppress
1349 newNodesToSuppress = []
1350 for node in nodesToSuppress:
1351 parent = parents[id(node)]
1352 # If we have suppressed the statement in a if statement (one-line if) or where statement
1353 # we must suppress the entire if/where statement even when simplifyStruct is False
1354 if tag(parent) == 'action-stmt':
1355 newNodesToSuppress.append(self.getParent(parent))
1356
1357 elif simplifyStruct:
1358 if tag(parent) == 'do-construct' and len(_nodesInDo(parent)) == 0:
1359 newNodesToSuppress.append(parent)
1360 elif tag(parent) == 'if-block':
1361 parPar = self.getParent(parent)
1362 if len(_nodesInIf(parPar)) == 0:
1363 newNodesToSuppress.append(parPar)
1364 elif tag(parent) == 'where-block':
1365 parPar = self.getParent(parent)
1366 if len(_nodesInWhere(parPar)) == 0:
1367 newNodesToSuppress.append(parPar)
1368 elif tag(parent) == 'selectcase-block':
1369 parPar = self.getParent(parent)
1370 if len(_nodesInCase(parPar)) == 0:
1371 newNodesToSuppress.append(parPar)
1372
1373 constructNodes, otherNodes = [], []
1374 for nnn in newNodesToSuppress:
1375 if tag(nnn).endswith('-construct'):
1376 if nnn not in constructNodes:
1377 constructNodes.append(nnn)
1378 else:
1379 if nnn not in otherNodes:
1380 otherNodes.append(nnn)
1381 # suppress all statements at once
1382 if len(otherNodes) > 0:
1383 self.removeStmtNode(otherNodes, simplifyVar, simplifyStruct)
1384 # suppress construct nodes one by one (recursive call)
1385 for nnn in constructNodes:
1386 self.removeConstructNode(nnn, simplifyVar, simplifyStruct)
1387
1388 @debugDecor
1389 def removeConstructNode(self, node, simplifyVar, simplifyStruct):
1390 """
1391 This function removes a construct node and:
1392 - suppress variable that became useless (if simplifyVar is True)
1393 - suppress outer loop/if if useless (if simplifyStruct is True)
1394 :param node: node representing the statement to remove
1395 :param simplifyVar: try to simplify code (if we delete "CALL FOO(X)" and if X not used
1396 else where, we also delete it; or if the call was alone inside a
1397 if-then-endif construct, with simplifyStruct=True, the construct is also
1398 removed, and variables used in the if condition are also checked...)
1399 :param simplifyStruct: try to simplify code (if we delete "CALL FOO(X)" and if the call was
1400 alone inside a if-then-endif construct, the construct is also
1401 removed, and variables used in the if condition
1402 (with simplifyVar=True) are also checked...)
1403
1404 If a statement is passed, it is suppressed by removeStmtNode
1405 """
1406 assert tag(node).endswith('-stmt') or tag(node).endswith('-construct'), \
1407 "Don't know how to suppress only a part of a structure or of a statement"
1408
1409 # This function removes inner statement to give a chance to identify and suppress unused
1410 # variables
1411 # During this step, nodes are suppressed with simplifyStruct=False to prevent infinite loops
1412 # then the actual node is removed using removeStmtNode
1413
1414 if tag(node).endswith('-construct'):
1415 # inner nodes
1416 nodes = {'do-construct': _nodesInDo,
1417 'if-construct': _nodesInIf,
1418 'where-construct': _nodesInWhere,
1419 'selectcase-construct': _nodesInCase}[tag(node)](node)
1420 # sort nodes by type
1421 constructNodes, otherNodes = [], []
1422 for nnn in nodes:
1423 if tag(nnn).endswith('-construct'):
1424 constructNodes.append(nnn)
1425 else:
1426 otherNodes.append(nnn)
1427 # suppress all statements at once
1428 self.removeStmtNode(otherNodes, simplifyVar, False)
1429 # suppress construct nodes one by one (recursive call)
1430 for nnn in constructNodes:
1431 self.removeConstructNode(nnn, simplifyVar, False)
1432 # suppress current node
1433 self.removeStmtNode(node, simplifyVar, simplifyStruct)
1434 else:
1435 # At least a-stmt, print-stmt
1436 self.removeStmtNode(node, simplifyVar, simplifyStruct)
1437
1438 @staticmethod
1439 @debugDecor
1440 def createDoConstruct(loopVariables, indent=0, concurrent=False):
1441 """
1442 :param loopVariables: ordered dictionnary with loop variables as key and bounds as values.
1443 Bounds are expressed with a 2-tuple.
1444 Keys must be in the same order as the order used when addressing an
1445 element: if loopVariables.keys is [JI, JK], arrays are
1446 addressed with (JI, JK)
1447 :param indent: current indentation
1448 :param concurrent: if False, output is made of nested 'DO' loops
1449 if True, output is made of a single 'DO CONCURRENT' loop
1450 :return: (inner, outer, extraindent) with
1451 - inner the inner do-construct where statements must be added
1452 - outer the outer do-construct to be inserted somewhere
1453 - extraindent the number of added indentation
1454 (2 if concurrent else 2*len(loopVariables))
1455 """
1456 if concurrent:
1457 # <f:do-construct>
1458 # <f:do-stmt>DO CONCURRENT (
1459 # <f:forall-triplet-spec-LT>
1460 # <f:forall-triplet-spec>
1461 # <f:V><f:named-E><f:N><f:n>JIJ</f:n></f:N></f:named-E></f:V>=
1462 # <f:lower-bound><f:named-E><f:N><f:n>IIJB</f:n>
1463 # </f:N></f:named-E></f:lower-bound>:
1464 # <f:upper-bound><f:named-E><f:N><f:n>IIJE</f:n>
1465 # </f:N></f:named-E></f:upper-bound>
1466 # </f:forall-triplet-spec>,
1467 # <f:forall-triplet-spec>
1468 # <f:V><f:named-E><f:N><f:n>JK</f:n></f:N></f:named-E></f:V>=
1469 # <f:lower-bound><f:literal-E><f:l>1</f:l></f:literal-E></f:lower-bound>:
1470 # <f:upper-bound><f:named-E><f:N><f:n>IKT</f:n>
1471 # </f:N></f:named-E></f:upper-bound>
1472 # </f:forall-triplet-spec>
1473 # </f:forall-triplet-spec-LT>)
1474 # </f:do-stmt>
1475 # statements
1476 # <f:end-do-stmt>END DO</f:end-do-stmt>
1477 # </f:do-construct>
1478 triplets = []
1479 # Better for vectorisation with some compilers
1480 for var, (lo, up) in list(loopVariables.items())[::-1]:
1481 nodeV = createElem('V', tail='=')
1482 nodeV.append(createExprPart(var))
1483 lower, upper = createArrayBounds(lo, up, 'DOCONCURRENT')
1484
1485 triplet = createElem('forall-triplet-spec')
1486 triplet.extend([nodeV, lower, upper])
1487
1488 triplets.append(triplet)
1489
1490 tripletLT = createElem('forall-triplet-spec-LT', tail=')')
1491 for triplet in triplets[:-1]:
1492 triplet.tail = ', '
1493 tripletLT.extend(triplets)
1494
1495 dostmt = createElem('do-stmt', text='DO CONCURRENT (', tail='\n')
1496 dostmt.append(tripletLT)
1497 enddostmt = createElem('end-do-stmt', text='END DO')
1498
1499 doconstruct = createElem('do-construct', tail='\n')
1500 doconstruct.extend([dostmt, enddostmt])
1501 inner = outer = doconstruct
1502 doconstruct[0].tail += (indent + 2) * ' ' # Indentation for the statement after DO
1503 else:
1504 # <f:do-construct>
1505 # <f:do-stmt>DO
1506 # <f:do-V><f:named-E><f:N><f:n>JRR</f:n></f:N></f:named-E></f:do-V> =
1507 # <f:lower-bound><f:literal-E><f:l>1</f:l></f:literal-E></f:lower-bound>:
1508 # <f:upper-bound><f:named-E><f:N><f:n>IKT</f:n></f:N></f:named-E></f:upper-bound>
1509 # </f:do-stmt>\n
1510 # statements \n
1511 # <f:end-do-stmt>END DO</f:end-do-stmt>
1512 # </f:do-construct>\n
1513 def makeDo(var, lo, up):
1514 doV = createElem('do-V', tail='=')
1515 doV.append(createExprPart(var))
1516 lower, upper = createArrayBounds(lo, up, 'DO')
1517
1518 dostmt = createElem('do-stmt', text='DO ', tail='\n')
1519 dostmt.extend([doV, lower, upper])
1520
1521 enddostmt = createElem('end-do-stmt', text='END DO')
1522
1523 doconstruct = createElem('do-construct', tail='\n')
1524 doconstruct.extend([dostmt, enddostmt])
1525 return doconstruct
1526
1527 outer = None
1528 inner = None
1529 for i, (var, (lo, up)) in enumerate(list(loopVariables.items())[::-1]):
1530 doconstruct = makeDo(var, lo, up)
1531 # Indentation for the statement after DO
1532 doconstruct[0].tail += (indent + 2 * i + 2) * ' '
1533 if outer is None:
1534 outer = doconstruct
1535 inner = doconstruct
1536 else:
1537 inner.insert(1, doconstruct)
1538 inner = doconstruct
1539 # Indentation for the ENDDO statement
1540 doconstruct.tail += (indent + 2 * i - 2) * ' '
1541 return inner, outer, 2 if concurrent else 2 * len(loopVariables)
1542
1543 @staticmethod
1544 @debugDecor
1545 def insertInList(pos, item, parent):
1546 """
1547 :param pos: insertion position
1548 :param item: item to add to the list
1549 :param parent: the parent of item (the list)
1550 """
1551 # insertion
1552 if pos < 0:
1553 pos = len(parent) + 1 + pos
1554 parent.insert(pos, item)
1555 if len(parent) > 1:
1556 i = list(parent).index(item) # effective position
1557 if i == len(parent) - 1:
1558 # The item is the last one
1559 parent[i - 1].tail = ', '
1560 else:
1561 parent[i].tail = ', '
1562
1563 @debugDecor
1564 def removeFromList(self, item, itemPar):
1565 """
1566 :param item: item to remove from list
1567 :param itemPar: the parent of item (the list)
1568 """
1569
1570 nodesToSuppress = [item]
1571
1572 # Suppression of the comma
1573 i = list(itemPar).index(item)
1574 if item.tail is not None and ',' in item.tail:
1575 # There's a comma just after the node
1576 tail = item.tail
1577 item.tail = tail.replace(',', '')
1578 elif i != 0 and ',' in itemPar[i - 1].tail:
1579 # There's a comma just before the node
1580 tail = itemPar[i - 1].tail
1581 itemPar[i - 1].tail = tail.replace(',', '')
1582 else:
1583 found = False
1584 # We look for a comma in the first node after the current node that
1585 # is not after another item of the list
1586 j = i + 1
1587 while j < len(itemPar) and not found:
1588 if nonCode(itemPar[j]):
1589 # This is a candidate
1590 if itemPar[j].tail is not None and ',' in itemPar[j].tail:
1591 # Comma found and suppressed
1592 found = True
1593 tail = itemPar[j].tail
1594 itemPar[j].tail = tail.replace(',', '')
1595 else:
1596 j += 1
1597 else:
1598 # This is another item
1599 break
1600
1601 # We look for a comma in the last node before the current node that
1602 # is not a comment or a contiuation character
1603 j = i - 1
1604 while j >= 0 and not found:
1605 if itemPar[j].tail is not None and ',' in itemPar[j].tail:
1606 # Comma found and suppressed
1607 found = True
1608 tail = itemPar[j].tail
1609 itemPar[j].tail = tail.replace(',', '')
1610 else:
1611 if nonCode(itemPar[j]):
1612 # We can search before
1613 j -= 1
1614 else:
1615 # This is another item
1616 break
1617
1618 if not found and \
1619 len([e for e in itemPar if not nonCode(e)]) != 1:
1620 raise RuntimeError("Something went wrong here....")
1621
1622 # Suppression of continuation characters
1623 if i + 1 < len(itemPar) and tag(itemPar[i + 1]) == 'cnt':
1624 # Node is followed by a continuation character
1625 reason = 'lastOnLine'
1626 # If the node is followed by a continuation character and is just after another
1627 # continuation character, we must supress the continuation character which is after.
1628 # USE MODD, ONLY: X, &
1629 # Y, & !Variable to suppress, with its '&' character
1630 # Z
1631 # In addition, if the character found before is at the begining of the line,
1632 # it must also be removed. To know if it is at the begining of the line,
1633 # we can check if another continuation character is before.
1634 # USE MODD, ONLY: X, &
1635 # & Y, & !Variable to suppress, with both '&' characters
1636 # & Z
1637 elif len([itemPar[j] for j in range(i + 1, len(itemPar)) if not nonCode(itemPar[j])]) == 0:
1638 # Node is the last of the list
1639 reason = 'last'
1640 # If the removed node is the last of the list and a continuation character
1641 # is just before. We must suppress it.
1642 # USE MODD, ONLY: X, &
1643 # Y !Variable to suppress, with the preceding '&'
1644 # In addition, if the character found before is at the begining of the line,
1645 # the preceding one must also be removed.
1646 # USE MODD, ONLY: X, &
1647 # & Y !Variable to suppress, with 2 '&'
1648 else:
1649 # We must not suppress '&' characters
1650 reason = None
1651 if reason is not None:
1652 def _getPrecedingCnt(itemPar, i):
1653 """
1654 Return the index of the preceding node which is a continuation character
1655 :param itemPar: the list containig the node to suppress
1656 :param i: the index of the current node, i-1 is the starting index for the search
1657 :return: a tuple with three elements:
1658 - the node containing the preceding '&' character
1659 - the parent of the node containing the preceding '&' character
1660 - index of the preceding '&' in the parent (previsous element
1661 of the tuple)
1662 Note:
1663 - In the general case the preceding '&' belongs to the same list:
1664 USE MODD, ONLY: X, &
1665 Y
1666 - But it exists a special case, where the preceding '&' don't belong to
1667 the same list (in the following example, both '&' are attached to the parent):
1668 USE MODD, ONLY: &
1669 & X
1670 """
1671 j = i - 1
1672 while j >= 0 and tag(itemPar[j]) == 'C':
1673 j -= 1
1674 if j >= 0 and tag(itemPar[j]) == 'cnt':
1675 return itemPar[j], itemPar, j
1676 if j == -1:
1677 # In the following special case, the '&' don't belong to the list but are
1678 # siblings of the list.
1679 # USE MODD, ONLY: &
1680 # & X
1681 siblings = self.getSiblings(itemPar, before=True, after=False)
1682 j2 = len(siblings) - 1
1683 while j2 >= 0 and tag(siblings[j2]) == 'C':
1684 j2 -= 1
1685 if j2 >= 0 and tag(siblings[j2]) == 'cnt':
1686 return siblings[j2], siblings, j2
1687 return None, None, None
1688 # We test if the preceding node (excluding comments) is a continuation character
1689 precCnt, newl, j = _getPrecedingCnt(itemPar, i)
1690 if precCnt is not None:
1691 # Preceding node is a continuation character
1692 nodesToSuppress.append(precCnt if reason == 'last' else itemPar[i + 1])
1693 if j is not None:
1694 precCnt2, _, _ = _getPrecedingCnt(newl, j)
1695 if precCnt2 is not None:
1696 # There is another continuation character before
1697 nodesToSuppress.append(precCnt2 if reason == 'last' else precCnt)
1698
1699 # Suppression of nodes
1700 for node in nodesToSuppress:
1701 # Get the tail of the previous children of the list and append the item's tail
1702 # to be removed
1703 if node in itemPar:
1704 parent = itemPar
1705 else:
1706 # parent must be recomputed because previsous removal may have change it
1707 parent = self.getParent(itemPar)
1708 i = list(parent).index(node)
1709 if i != 0 and node.tail is not None:
1710 if parent[i - 1].tail is None:
1711 parent[i - 1].tail = ''
1712 parent[i - 1].tail = parent[i - 1].tail + node.tail
1713 parent.remove(node)
inlineContainedSubroutines(self, simplify=False, loopVar=None)
removePrints(self, simplify=False)
evalFalseIfStmt(self, nodes, simplify=False)
removeConstructNode(self, node, simplifyVar, simplifyStruct)
inline(self, subContained, callStmt, mainScope, simplify=False, loopVar=None)
isNodeInProcedure(self, node, procList)
Definition statements.py:74
removeArraySyntax(self, concurrent=False, useMnhExpand=True, everywhere=True, loopVar=None, reuseLoop=True, funcList=None, updateMemSet=False, updateCopy=False, addAccIndependentCollapse=True)
setFalseIfStmt(self, flags, simplify=False)
checkEmptyParensInCall(self, mustRaise=False)
insertStatement(self, stmt, first)
createDoConstruct(loopVariables, indent=0, concurrent=False)
insertInList(pos, item, parent)
checkOpInCall(self, mustRaise=False)
removeStmtNode(self, nodes, simplifyVar, simplifyStruct)
removeCall(self, callName, simplify=False)
removeFromList(self, item, itemPar)
_nodesInWhere(whereNode)
Definition statements.py:29
_nodesInCase(caseNode)
Definition statements.py:54