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