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 icom = 1
802 while tag(node[icom]) == 'C':
803 if node[icom].text.startswith('!$acc'):
804 if 'routine' in node[icom].text:
805 node.remove(node[icom])
806 else:
807 icom += 1
808 else:
809 node.remove(node[icom])
810
811 # Variable correspondance
812 # For each dummy argument, we look for the calling arg name and shape
813 # CALL FOO(Z(:))
814 # SUBROUTINE FOO(P)
815 # variable = {'P':{'name': 'Z', dim=[':']}}
816 vartable = {} # ordered dict
817 for argN in subContained.findall('.//{*}subroutine-stmt/{*}dummy-arg-LT/{*}arg-N'):
818 vartable[alltext(argN).upper()] = None # Not present by default
819 for iarg, arg in enumerate(callStmt.findall('.//{*}arg')):
820 key = arg.find('.//{*}arg-N')
821 if key is not None:
822 # arg is VAR=value
823 dummyName = alltext(key).upper()
824 argnode = arg[1]
825 else:
826 dummyName = list(vartable.keys())[iarg]
827 argnode = arg[0]
828 nodeRLTarray = argnode.findall('.//{*}R-LT/{*}array-R')
829 if len(nodeRLTarray) > 0:
830 # array
831 if len(nodeRLTarray) > 1 or \
832 argnode.find('./{*}R-LT/{*}array-R') is None or \
833 tag(argnode) != 'named-E':
834 # Only simple cases are treated
835 raise PYFTError('Argument to complicated: ' + str(alltext(argnode)))
836 dim = nodeRLTarray[0].find('./{*}section-subscript-LT')[:]
837 else:
838 dim = None
839 if dim is None:
840 # A%B => argname = 'A%B'
841 # Z(1) => argname = 'Z(1)'
842 argname = "".join(argnode.itertext())
843 else:
844 # Z(:) => argname = 'Z'
845 tmp = copy.deepcopy(argnode)
846 nodeRLT = tmp.find('./{*}R-LT')
847 nodeRLT.remove(nodeRLT.find('./{*}array-R'))
848 argname = "".join(tmp.itertext())
849 vartable[dummyName] = {'node': argnode, 'name': argname, 'dim': dim}
850
851 # Look for PRESENT(var) and replace it by True when variable is present, by False otherwise
852 for dummyName in [dummyName for (dummyName, value) in vartable.items()
853 if value is not None]:
854 setPRESENTby(node, dummyName, True)
855 for dummyName in [dummyName for (dummyName, value) in vartable.items()
856 if value is None]:
857 setPRESENTby(node, dummyName, False)
858
859 # Look for usage of variable not present and delete corresponding code
860 for dummyName in [dummyName for (dummyName, value) in vartable.items() if value is None]:
861 for nodeN in [nodeN for nodeN in node.findall('.//{*}named-E/{*}N')
862 if n2name(nodeN).upper() == dummyName]:
863 removed = False
864 # Parent is the named-E node, we need at least the upper level
865 par = node.getParent(nodeN, level=2)
866 allreadySuppressed = []
867 while par and not removed and par not in allreadySuppressed:
868 toSuppress = None
869 tagName = tag(par)
870 if tagName in ('a-stmt', 'print-stmt'):
871 # Context 1: an a-stmt of type E1 = E2
872 toSuppress = par
873 elif tagName == 'call-stmt':
874 # We should rewrite the call statement without this optional argument
875 # But it is not easy: we must check that the argument is really optional
876 # for the called routine and we must add (if not already present)
877 # keywords for following arguments
878 raise NotImplementedError('call-stmt not (yet?) implemented')
879 elif tagName in ('if-stmt', 'where-stmt'):
880 # Context 2: an if-stmt of type : IF(variable) ...
881 toSuppress = par
882 elif tagName in ('if-then-stmt', 'else-if-stmt', 'where-construct-stmt',
883 'else-where-stmt'):
884 # Context 3: an if-block of type : IF(variable) THEN...
885 # We delete the entire construct
886 toSuppress = node.getParent(par, 2)
887 elif tagName in ('select-case-stmt', 'case-stmt'):
888 # Context 4: SELECT CASE (variable)... or CASE (variable)
889 # We deleted the entire construct
890 toSuppress = node.getParent(par, 2)
891 elif tagName.endswith('-block') or tagName.endswith('-stmt') or \
892 tagName.endswith('-construct'):
893 # action-stmt, do-stmt, forall-construct-stmt, forall-stmt,
894 # if-block, where-block, selectcase-block,
895 # must not contain directly
896 # the variable but must contain other statements using the variable.
897 # We target the inner statement in the previous cases.
898 # Some cases may have been overlooked and should be added above.
899 raise PYFTError(("We shouldn't be here. A case may have been " +
900 "overlooked (tag={tag}).".format(tag=tagName)))
901 if toSuppress is not None:
902 removed = True
903 if toSuppress not in allreadySuppressed:
904 # We do not simplify variables to prevent side-effect with
905 # the variable renaming
906 node.removeStmtNode(toSuppress, False, simplify)
907 allreadySuppressed.extend(list(toSuppress.iter()))
908 else:
909 par = node.getParent(par)
910
911 # Loop on the dummy argument
912 for name, dummy in vartable.items():
913 # Loop on all variables in the contained routine
914 # It is important to build again the list of nodes, because it may have
915 # changed during the previous dummy argument substitution
916 for namedE in [namedE for namedE in node.findall('.//{*}named-E/{*}N/{*}n/../..')
917 if n2name(namedE.find('{*}N')).upper() == name]:
918 # 0 Concatenation of n nodes (a name could be split over several n nodes)
919 nodeN = namedE.find('./{*}N')
920 ns = nodeN.findall('./{*}n')
921 ns[0].text = n2name(nodeN)
922 for nnn in ns[1:]:
923 nodeN.remove(nnn)
924
925 # 1 We get info about variables (such as declared in the main or in
926 # the contained routines)
927 descMain = varList.restrict(mainScope.path, True).findVar(dummy['name'])
928 descSub = varList.restrict(subContained.path, True).findVar(name)
929 # In case variable is used for the declaration of another variable,
930 # we must update descSub
931 for var in varList:
932 if var['as'] is not None:
933 var['as'] = [[re.sub(r'\b' + name + r'\b', dummy['name'], dim[i])
934 if dim[i] is not None else None
935 for i in (0, 1)]
936 for dim in var['as']]
937
938 # 3 We select the indexes (only for array argument and not structure argument
939 # containing an array)
940 # using the occurrence to replace inside the subcontained routine body
941 nodeRLT = namedE.find('./{*}R-LT')
942 if nodeRLT is not None and tag(nodeRLT[0]) != 'component-R':
943 # The variable name is immediately followed by a parenthesis
944 assert tag(nodeRLT[0]) in ('array-R', 'parens-R'), 'Internal error'
945 slices = nodeRLT[0].findall('./{*}section-subscript-LT/' +
946 '{*}section-subscript')
947 slices += nodeRLT[0].findall('./{*}element-LT/{*}element')
948 else:
949 # No parenthesis
950 if (descMain is not None and len(descMain['as']) > 0) or \
951 len(descSub['as']) > 0 or dummy['dim'] is not None:
952 # No parenthesis, but this is an array, we add as many ':' as needed
953 if len(descSub['as']) > 0:
954 ndim = len(descSub['as'])
955 else:
956 # ELEMENTAL routine, dummy arg is scalar
957 if dummy['dim'] is not None:
958 # We use the variable passed as argument because
959 # parenthesis were used
960 ndim = len([d for d in dummy['dim'] if ':' in alltext(d)])
961 else:
962 # We use the declared version in main
963 ndim = len(descMain['as'])
964 ns[0].text += '(' + (', '.join([':'] * ndim)) + ')'
965 updatedNamedE = createExprPart(alltext(namedE))
966 namedE.tag = updatedNamedE.tag
967 namedE.text = updatedNamedE.text
968 for nnn in namedE[:]:
969 namedE.remove(nnn)
970 namedE.extend(updatedNamedE[:])
971 slices = namedE.find('./{*}R-LT')[0].findall(
972 './{*}section-subscript-LT/{*}section-subscript')
973 else:
974 # This is not an array
975 slices = []
976
977 # 4 New name (the resultig xml is not necessarily a valid fxtran xml)
978 namedE.find('./{*}N')[0].text = dummy['name']
979
980 # 5 We update the indexes to take into account a different declaration
981 # (lower bound especially)
982 # in the main and in the contained routines.
983 # Moreover, we could need to add indexes
984 if len(slices) > 0:
985 # This is an array
986 for isl, sl in enumerate(slices):
987 # 0 Compute bounds for array
988 if len(descSub['as']) == 0 or descSub['as'][isl][1] is None:
989 # ELEMENTAL or array with implicit shape
990 for i in (0, 1): # 0 for lower bound and 1 for upper bound
991 if dummy['dim'] is not None:
992 # Parenthesis in the call statement
993 tagName = './{*}lower-bound' if i == 0 else './{*}upper-bound'
994 descSub[i] = dummy['dim'][isl].find(tagName)
995 else:
996 descSub[i] = None
997 if descSub[i] is not None:
998 # lower/upper limit was given in the call statement
999 descSub[i] = alltext(descSub[i])
1000 else:
1001 # if available we take lower/upper limit set in the declaration
1002 if descMain is not None and descMain['as'][isl][1] is not None:
1003 # Declaration found in main, and not using implicit shape
1004 descSub[i] = descMain['as'][isl][i]
1005 if i == 0 and descSub[i] is None:
1006 descSub[i] = '1' # Default FORTRAN value
1007 else:
1008 descSub[i] = "L" if i == 0 else "U"
1009 descSub[i] += "BOUND({name}, {isl})".format(
1010 name=dummy['name'], isl=isl + 1)
1011 else:
1012 descSub[0] = descSub['as'][isl][0]
1013 if descSub[0] is None:
1014 descSub[0] = '1' # Default FORTRAN value
1015 descSub[1] = descSub['as'][isl][1]
1016
1017 # 1 Offset computation
1018 # if only a subset is passed
1019 # and/or if lower bound of array is different in main and in sub
1020 # contained routine
1021 # REAL, DIMENSION(M1:M2):: Z; CALL FOO(N1:N2)
1022 # SUBROUTINE FOO(P); REAL, DIMENSION(K1:K2):: P; P(I1:I2)
1023 # If M1 or K1 is not set, they defaults to 1; if not set, N1 defaults to
1024 # M1 and I1 to K1
1025 # P(I1:I2)=Z(I1-K1+N1:I2-K1+N1)
1026 offset = 0
1027 if dummy['dim'] is not None and \
1028 not alltext(dummy['dim'][isl]).strip().startswith(':'):
1029 offset = alltext(dummy['dim'][isl].find('./{*}lower-bound'))
1030 else:
1031 if descMain is not None:
1032 offset = descMain['as'][isl][0]
1033 if offset is None:
1034 offset = '1' # Default FORTRAN value
1035 elif offset.strip().startswith('-'):
1036 offset = '(' + offset + ')'
1037 else:
1038 offset = "LBOUND({name}, {isl})".format(
1039 name=dummy['name'], isl=isl + 1)
1040 if offset.upper() == descSub[0].upper():
1041 offset = 0
1042 else:
1043 if descSub[0].strip().startswith('-'):
1044 offset += '- (' + descSub[0] + ')'
1045 else:
1046 offset += '-' + descSub[0]
1047
1048 # 2 Update index with the offset and add indexes instead of ':'
1049 if offset != 0:
1050 if tag(sl) == 'element' or \
1051 (tag(sl) == 'section-subscript' and ':' not in alltext(sl)):
1052 # Z(I) or last index of Z(:, I)
1053 bounds = sl
1054 else:
1055 low = sl.find('./{*}lower-bound')
1056 if low is None:
1057 low = createElem('lower-bound', tail=sl.text)
1058 low.append(createExprPart(descSub[0]))
1059 sl.text = None
1060 sl.insert(0, low)
1061 up = sl.find('./{*}upper-bound')
1062 if up is None:
1063 up = createElem('upper-bound')
1064 up.append(createExprPart(descSub[1]))
1065 sl.append(up)
1066 bounds = [low, up]
1067 for bound in bounds:
1068 # bound[-1] is a named-E, literal-E, op-E...
1069 if bound[-1].tail is None:
1070 bound[-1].tail = ''
1071 bound[-1].tail += '+' + offset # Not valid fxtran xml
1072
1073 # We must add extra indexes
1074 # CALL FOO(Z(:,1))
1075 # SUBROUTINE FOO(P); REAL, DIMENSION(K1:K2):: P
1076 # P(I) => Z(I, 1)
1077 if dummy['dim'] is not None and len(dummy['dim']) > len(slices):
1078 slices[-1].tail = ', '
1079 par = node.getParent(slices[-1])
1080 par.extend(dummy['dim'][len(slices):])
1081
1082 # 6 Convert (wrong) xml into text and into xml again (to obtain a valid fxtran xml)
1083 # This double conversion is not sufficient in some case.
1084 # E.g. variable (N/n tag) replaced by real value
1085 updatedNamedE = createExprPart(alltext(namedE))
1086 namedE.tag = updatedNamedE.tag
1087 namedE.text = updatedNamedE.text
1088 for nnn in namedE[:]:
1089 namedE.remove(nnn)
1090 namedE.extend(updatedNamedE[:])
1091
1092 node.remove(node.find('./{*}subroutine-stmt'))
1093 node.remove(node.find('./{*}end-subroutine-stmt'))
1094
1095 # Add local var and use to main routine
1096 mainScope.addVar([[mainScope.path, var['n'], mainScope.varSpec2stmt(var), None]
1097 for var in localVarToAdd])
1098 mainScope.addModuleVar([[mainScope.path, n2name(useStmt.find('.//{*}module-N//{*}N')),
1099 [n2name(v.find('.//{*}N'))
1100 for v in useStmt.findall('.//{*}use-N')]]
1101 for useStmt in localUseToAdd])
1102
1103 # Remove call statement of the contained routines
1104 index = list(parent).index(callStmt)
1105 parent.remove(callStmt)
1106 if callStmt.tail is not None:
1107 if node[-1].tail is None:
1108 node[-1].tail = callStmt.tail
1109 else:
1110 node[-1].tail = node[-1].tail + callStmt.tail
1111 for node in node[::-1]:
1112 # node is a program-unit, we must insert the subelements
1113 parent.insert(index, node)
1114
1115 @debugDecor
1116 def setFalseIfStmt(self, flags, simplify=False):
1117 """
1118 Set to .FALSE. a given boolean fortran flag before removing the node if simplify is True
1119 :param flags: list of strings of flags to set to .FALSE.
1120 :param simplify: try to simplify code (if the .FALSE. was alone inside a if-then-endif
1121 construct, the construct is removed, and variables used in the if
1122 condition are also checked)
1123 """
1124 if isinstance(flags, str):
1125 flags = [flags]
1126 flags = [flag.upper() for flag in flags]
1127 for scope in self.getScopes(excludeKinds=['type']):
1128 singleFalseBlock, multipleFalseBlock = [], []
1129 # Loop on nodes composing the scope
1130 for node in scope:
1131 # Loop on condition nodes
1132 for cond in node.findall('.//{*}condition-E'):
1133 found = False
1134 for namedE in [namedE for namedE
1135 in node.findall('.//{*}condition-E//{*}named-E')
1136 if alltext(namedE).upper() in flags]:
1137 # This named-E must be replaced by .FALSE.
1138 found = True
1139 namedE.tag = '{{{NAMESPACE}}}literal-E'
1140 namedE.text = '.FALSE.'
1141 for item in list(namedE):
1142 namedE.remove(item)
1143 if found:
1144 nodeOpE = cond.find('./{*}op-E')
1145 if nodeOpE is not None:
1146 # Multiple flags conditions
1147 multipleFalseBlock.append(nodeOpE)
1148 else:
1149 # Solo condition
1150 # <if-block><if-then-stmt>
1151 if tag(scope.getParent(cond)).startswith('if-stmt'):
1152 scope.changeIfStatementsInIfConstructs(scope.getParent(cond))
1153 singleFalseBlock.append(scope.getParent(cond, level=2))
1154 if simplify:
1155 scope.removeStmtNode(singleFalseBlock, simplify, simplify)
1156 scope.evalFalseIfStmt(multipleFalseBlock, simplify)
1157
1158 @debugDecor
1159 def evalFalseIfStmt(self, nodes, simplify=False):
1160 """
1161 Evaluate if-stmt with multiple op-E and remove the nodes if only .FALSE. are present
1162 :param nodes: list of nodes of type op-E to evaluate (containing .FALSE.)
1163 :param simplify: try to simplify code (if if-block is removed, variables used in the
1164 if condition are also checked)
1165 """
1166 nodesTorm = []
1167 for node in nodes:
1168 toRemove = True
1169 for nnn in node:
1170 if tag(nnn) == 'op' or (tag(nnn) == 'literal-E' and '.FALSE.' in alltext(nnn)):
1171 pass
1172 else:
1173 toRemove = False
1174 break
1175 if toRemove:
1176 # <if-block><if-then-stmt><condition-E>
1177 nodesTorm.append(self.getParent(level=3))
1178 self.removeStmtNode(nodesTorm, simplify, simplify)
1179
1180 @debugDecor
1181 def checkOpInCall(self, mustRaise=False):
1182 """
1183 :param mustRaise: True to raise
1184 Issue a logging.warning if some call arguments are operations
1185 If mustRaise is True, issue a logging.error instead and raise an error
1186 """
1187 ok = True
1188 log = logging.error if mustRaise else logging.warning
1189 for arg in self.findall('.//{*}call-stmt/{*}arg-spec/{*}arg/{*}op-E'):
1190 log(("The call argument {} is an operation, in file '{}'"
1191 ).format(alltext(arg).replace('\n', ' \\n '), self.getFileName()))
1192 ok = False
1193 if not ok and mustRaise:
1194 raise PYFTError(("There are call arguments which are operations in file '{}'"
1195 ).format(self.getFileName()))
1196
1197 @debugDecor
1198 def checkEmptyParensInCall(self, mustRaise=False):
1199 """
1200 :param mustRaise: True to raise
1201 Issue a logging.warning if some call arguments are arrays with empty parens
1202 Example: CALL FOO(A(:))
1203 If mustRaise is True, issue a logging.error instead and raise an error
1204 """
1205 ok = True
1206 log = logging.error if mustRaise else logging.warning
1207 for sslt in self.findall('.//{*}call-stmt/{*}arg-spec/{*}arg/{*}named-E/' +
1208 '{*}R-LT/{*}array-R/{*}section-subscript-LT'):
1209 if all(alltext(ss) == ':' for ss in sslt.findall('./{*}section-subscript')):
1210 arg = self.getParent(sslt, 3)
1211 log(("The call argument {} is an array with empty parens, in file '{}'"
1212 ).format(alltext(arg).replace('\n', ' \\n '), self.getFileName()))
1213 ok = False
1214 if not ok and mustRaise:
1215 raise PYFTError(("There are call arguments which are arrays " +
1216 "with empty parens in file '{}'").format(self.getFileName()))
1217
1218 @debugDecor
1219 def insertStatement(self, stmt, first):
1220 """
1221 Insert a statement to be executed first (or last)
1222 :param stmt: statement to insert
1223 :param first: True to insert it in first position, False to insert it in last position
1224 :return: the index of the stmt inserted in scope
1225 """
1226 # pylint: disable=unsubscriptable-object
1227 if first:
1228 # Statement must be inserted after all use, T-decl, implicit-non-stmt,
1229 # interface and cray pointers
1230 nodes = self.findall('./{*}T-decl-stmt') + self.findall('./{*}use-stmt') + \
1231 self.findall('./{*}implicit-none-stmt') + \
1232 self.findall('./{*}interface-construct') + \
1233 self.findall('./{*}pointer-stmt')
1234 if len(nodes) > 0:
1235 # Insertion after the last node
1236 index = max([list(self).index(n) for n in nodes]) + 1
1237 else:
1238 # Insertion after the subroutine or function node
1239 index = 2
1240 # If an include statements follows, it certainly contains an interface
1241 while (tag(self[index]) in ('C', 'include', 'include-stmt') or
1242 (tag(self[index]) == 'cpp' and self[index].text.startswith('#include'))):
1243 if not self[index].text.startswith('!$acc'):
1244 index += 1
1245 else:
1246 break
1247 else:
1248 # Statement must be inserted before the contains statement
1249 contains = self.find('./{*}contains-stmt')
1250 if contains is not None:
1251 # Insertion before the contains statement
1252 index = list(self).index(contains)
1253 else:
1254 # Insertion before the end subroutine or function statement
1255 index = -1
1256 if self[index - 1].tail is None:
1257 self[index - 1].tail = '\n'
1258 elif '\n' not in self[index - 1].tail:
1259 self[index - 1].tail += '\n'
1260 self.insert(index, stmt)
1261 return index
1262
1263 @debugDecor
1264 def removeStmtNode(self, nodes, simplifyVar, simplifyStruct):
1265 """
1266 This function removes a statement node and:
1267 - suppress variable that became useless (if simplifyVar is True)
1268 - suppress outer loop/if if useless (if simplifyStruct is True)
1269 :param nodes: node (or list of nodes) to remove
1270 :param simplifyVar: try to simplify code (if we delete "CALL FOO(X)" and if X not used
1271 else where, we also delete it; or if the call was alone inside a
1272 if-then-endif construct, with simplifyStruct=True, the construct is also
1273 removed, and variables used in the if condition are also checked...)
1274 :param simplifyStruct: try to simplify code (if we delete "CALL FOO(X)" and if the call was
1275 alone inside a if-then-endif construct, the construct is also
1276 removed and variables used in the if condition
1277 (with simplifyVar=True) are also checked...)
1278 """
1279
1280 # In case the suppression of an if-stmt or where-stmt is asked,
1281 # we must start by the inner statement
1282 nodesToSuppress = []
1283 if not isinstance(nodes, list):
1284 nodes = [nodes]
1285 for node in nodes:
1286 if tag(node) in ('if-stmt', 'where-stmt'):
1287 action = node.find('./{*}action-stmt')
1288 if action is not None and len(action) != 0:
1289 nodesToSuppress.append(action[0])
1290 else:
1291 nodesToSuppress.append(node)
1292 elif tag(node) == '}action-stmt':
1293 if len(node) != 0:
1294 nodesToSuppress.append(node[0])
1295 else:
1296 nodesToSuppress.append(node)
1297 else:
1298 nodesToSuppress.append(node)
1299
1300 varToCheck = [] # List of variables to check for suppression
1301 if simplifyVar:
1302 # Loop to identify all the potential variables to remove
1303 for node in nodesToSuppress:
1304 scopePath = self.getScopePath(node)
1305 if tag(node) == 'do-construct':
1306 # Try to remove variables used in the loop
1307 varToCheck.extend([(scopePath, n2name(arg))
1308 for arg in node.find('./{*}do-stmt').findall('.//{*}N')])
1309 elif tag(node) in ('if-construct', 'if-stmt'):
1310 # Try to remove variables used in the conditions
1311 varToCheck.extend([(scopePath, n2name(arg))
1312 for arg in node.findall('.//{*}condition-E//{*}N')])
1313 elif tag(node) in ('where-construct', 'where-stmt'):
1314 # Try to remove variables used in the conditions
1315 varToCheck.extend([(scopePath, n2name(arg))
1316 for arg in node.findall('.//{*}mask-E//{*}N')])
1317 elif tag(node) == 'call-stmt':
1318 # We must check if we can suppress the variables used to call the subprogram
1319 varToCheck.extend([(scopePath, n2name(arg))
1320 for arg in node.findall('./{*}arg-spec//{*}N')])
1321 # And maybe, the subprogram comes from a module
1322 varToCheck.append((scopePath,
1323 n2name(node.find('./{*}procedure-designator//{*}N'))))
1324 elif tag(node) in ('a-stmt', 'print-stmt'):
1325 varToCheck.extend([(scopePath, n2name(arg)) for arg in node.findall('.//{*}N')])
1326 elif tag(node) == 'selectcase-construct':
1327 # Try to remove variables used in the selector and in conditions
1328 varToCheck.extend([(scopePath, n2name(arg))
1329 for arg in node.findall('.//{*}case-E//{*}N')])
1330 varToCheck.extend([(scopePath, n2name(arg))
1331 for arg in node.findall('.//{*}case-value//{*}N')])
1332
1333 # Node suppression
1334 parents = {} # cache
1335 for node in nodesToSuppress:
1336 parent = self.getParent(node)
1337 parents[id(node)] = parent
1338 newlines = '\n' * (alltext(node).count('\n') if tag(node).endswith('-construct') else 0)
1339 if node.tail is not None or len(newlines) > 0:
1340 previous = self.getSiblings(node, after=False)
1341 if len(previous) == 0:
1342 previous = parent
1343 else:
1344 previous = previous[-1]
1345 if previous.tail is None:
1346 previous.tail = ''
1347 previous.tail = (previous.tail.replace('\n', '') +
1348 (node.tail if node.tail is not None else ''))
1349 parent.remove(node)
1350
1351 # Variable simplification
1352 self.removeVarIfUnused(varToCheck, excludeDummy=True,
1353 excludeModule=True, simplify=simplifyVar)
1354
1355 # List the new nodes to suppress
1356 newNodesToSuppress = []
1357 for node in nodesToSuppress:
1358 parent = parents[id(node)]
1359 # If we have suppressed the statement in a if statement (one-line if) or where statement
1360 # we must suppress the entire if/where statement even when simplifyStruct is False
1361 if tag(parent) == 'action-stmt':
1362 newNodesToSuppress.append(self.getParent(parent))
1363
1364 elif simplifyStruct:
1365 if tag(parent) == 'do-construct' and len(_nodesInDo(parent)) == 0:
1366 newNodesToSuppress.append(parent)
1367 elif tag(parent) == 'if-block':
1368 parPar = self.getParent(parent)
1369 if len(_nodesInIf(parPar)) == 0:
1370 newNodesToSuppress.append(parPar)
1371 elif tag(parent) == 'where-block':
1372 parPar = self.getParent(parent)
1373 if len(_nodesInWhere(parPar)) == 0:
1374 newNodesToSuppress.append(parPar)
1375 elif tag(parent) == 'selectcase-block':
1376 parPar = self.getParent(parent)
1377 if len(_nodesInCase(parPar)) == 0:
1378 newNodesToSuppress.append(parPar)
1379
1380 constructNodes, otherNodes = [], []
1381 for nnn in newNodesToSuppress:
1382 if tag(nnn).endswith('-construct'):
1383 if nnn not in constructNodes:
1384 constructNodes.append(nnn)
1385 else:
1386 if nnn not in otherNodes:
1387 otherNodes.append(nnn)
1388 # suppress all statements at once
1389 if len(otherNodes) > 0:
1390 self.removeStmtNode(otherNodes, simplifyVar, simplifyStruct)
1391 # suppress construct nodes one by one (recursive call)
1392 for nnn in constructNodes:
1393 self.removeConstructNode(nnn, simplifyVar, simplifyStruct)
1394
1395 @debugDecor
1396 def removeConstructNode(self, node, simplifyVar, simplifyStruct):
1397 """
1398 This function removes a construct node and:
1399 - suppress variable that became useless (if simplifyVar is True)
1400 - suppress outer loop/if if useless (if simplifyStruct is True)
1401 :param node: node representing the statement to remove
1402 :param simplifyVar: try to simplify code (if we delete "CALL FOO(X)" and if X not used
1403 else where, we also delete it; or if the call was alone inside a
1404 if-then-endif construct, with simplifyStruct=True, the construct is also
1405 removed, and variables used in the if condition are also checked...)
1406 :param simplifyStruct: try to simplify code (if we delete "CALL FOO(X)" and if the call was
1407 alone inside a if-then-endif construct, the construct is also
1408 removed, and variables used in the if condition
1409 (with simplifyVar=True) are also checked...)
1410
1411 If a statement is passed, it is suppressed by removeStmtNode
1412 """
1413 assert tag(node).endswith('-stmt') or tag(node).endswith('-construct'), \
1414 "Don't know how to suppress only a part of a structure or of a statement"
1415
1416 # This function removes inner statement to give a chance to identify and suppress unused
1417 # variables
1418 # During this step, nodes are suppressed with simplifyStruct=False to prevent infinite loops
1419 # then the actual node is removed using removeStmtNode
1420
1421 if tag(node).endswith('-construct'):
1422 # inner nodes
1423 nodes = {'do-construct': _nodesInDo,
1424 'if-construct': _nodesInIf,
1425 'where-construct': _nodesInWhere,
1426 'selectcase-construct': _nodesInCase}[tag(node)](node)
1427 # sort nodes by type
1428 constructNodes, otherNodes = [], []
1429 for nnn in nodes:
1430 if tag(nnn).endswith('-construct'):
1431 constructNodes.append(nnn)
1432 else:
1433 otherNodes.append(nnn)
1434 # suppress all statements at once
1435 self.removeStmtNode(otherNodes, simplifyVar, False)
1436 # suppress construct nodes one by one (recursive call)
1437 for nnn in constructNodes:
1438 self.removeConstructNode(nnn, simplifyVar, False)
1439 # suppress current node
1440 self.removeStmtNode(node, simplifyVar, simplifyStruct)
1441 else:
1442 # At least a-stmt, print-stmt
1443 self.removeStmtNode(node, simplifyVar, simplifyStruct)
1444
1445 @staticmethod
1446 @debugDecor
1447 def createDoConstruct(loopVariables, indent=0, concurrent=False):
1448 """
1449 :param loopVariables: ordered dictionnary with loop variables as key and bounds as values.
1450 Bounds are expressed with a 2-tuple.
1451 Keys must be in the same order as the order used when addressing an
1452 element: if loopVariables.keys is [JI, JK], arrays are
1453 addressed with (JI, JK)
1454 :param indent: current indentation
1455 :param concurrent: if False, output is made of nested 'DO' loops
1456 if True, output is made of a single 'DO CONCURRENT' loop
1457 :return: (inner, outer, extraindent) with
1458 - inner the inner do-construct where statements must be added
1459 - outer the outer do-construct to be inserted somewhere
1460 - extraindent the number of added indentation
1461 (2 if concurrent else 2*len(loopVariables))
1462 """
1463 if concurrent:
1464 # <f:do-construct>
1465 # <f:do-stmt>DO CONCURRENT (
1466 # <f:forall-triplet-spec-LT>
1467 # <f:forall-triplet-spec>
1468 # <f:V><f:named-E><f:N><f:n>JIJ</f:n></f:N></f:named-E></f:V>=
1469 # <f:lower-bound><f:named-E><f:N><f:n>IIJB</f:n>
1470 # </f:N></f:named-E></f:lower-bound>:
1471 # <f:upper-bound><f:named-E><f:N><f:n>IIJE</f:n>
1472 # </f:N></f:named-E></f:upper-bound>
1473 # </f:forall-triplet-spec>,
1474 # <f:forall-triplet-spec>
1475 # <f:V><f:named-E><f:N><f:n>JK</f:n></f:N></f:named-E></f:V>=
1476 # <f:lower-bound><f:literal-E><f:l>1</f:l></f:literal-E></f:lower-bound>:
1477 # <f:upper-bound><f:named-E><f:N><f:n>IKT</f:n>
1478 # </f:N></f:named-E></f:upper-bound>
1479 # </f:forall-triplet-spec>
1480 # </f:forall-triplet-spec-LT>)
1481 # </f:do-stmt>
1482 # statements
1483 # <f:end-do-stmt>END DO</f:end-do-stmt>
1484 # </f:do-construct>
1485 triplets = []
1486 # Better for vectorisation with some compilers
1487 for var, (lo, up) in list(loopVariables.items())[::-1]:
1488 nodeV = createElem('V', tail='=')
1489 nodeV.append(createExprPart(var))
1490 lower, upper = createArrayBounds(lo, up, 'DOCONCURRENT')
1491
1492 triplet = createElem('forall-triplet-spec')
1493 triplet.extend([nodeV, lower, upper])
1494
1495 triplets.append(triplet)
1496
1497 tripletLT = createElem('forall-triplet-spec-LT', tail=')')
1498 for triplet in triplets[:-1]:
1499 triplet.tail = ', '
1500 tripletLT.extend(triplets)
1501
1502 dostmt = createElem('do-stmt', text='DO CONCURRENT (', tail='\n')
1503 dostmt.append(tripletLT)
1504 enddostmt = createElem('end-do-stmt', text='END DO')
1505
1506 doconstruct = createElem('do-construct', tail='\n')
1507 doconstruct.extend([dostmt, enddostmt])
1508 inner = outer = doconstruct
1509 doconstruct[0].tail += (indent + 2) * ' ' # Indentation for the statement after DO
1510 else:
1511 # <f:do-construct>
1512 # <f:do-stmt>DO
1513 # <f:do-V><f:named-E><f:N><f:n>JRR</f:n></f:N></f:named-E></f:do-V> =
1514 # <f:lower-bound><f:literal-E><f:l>1</f:l></f:literal-E></f:lower-bound>:
1515 # <f:upper-bound><f:named-E><f:N><f:n>IKT</f:n></f:N></f:named-E></f:upper-bound>
1516 # </f:do-stmt>\n
1517 # statements \n
1518 # <f:end-do-stmt>END DO</f:end-do-stmt>
1519 # </f:do-construct>\n
1520 def makeDo(var, lo, up):
1521 doV = createElem('do-V', tail='=')
1522 doV.append(createExprPart(var))
1523 lower, upper = createArrayBounds(lo, up, 'DO')
1524
1525 dostmt = createElem('do-stmt', text='DO ', tail='\n')
1526 dostmt.extend([doV, lower, upper])
1527
1528 enddostmt = createElem('end-do-stmt', text='END DO')
1529
1530 doconstruct = createElem('do-construct', tail='\n')
1531 doconstruct.extend([dostmt, enddostmt])
1532 return doconstruct
1533
1534 outer = None
1535 inner = None
1536 for i, (var, (lo, up)) in enumerate(list(loopVariables.items())[::-1]):
1537 doconstruct = makeDo(var, lo, up)
1538 # Indentation for the statement after DO
1539 doconstruct[0].tail += (indent + 2 * i + 2) * ' '
1540 if outer is None:
1541 outer = doconstruct
1542 inner = doconstruct
1543 else:
1544 inner.insert(1, doconstruct)
1545 inner = doconstruct
1546 # Indentation for the ENDDO statement
1547 doconstruct.tail += (indent + 2 * i - 2) * ' '
1548 return inner, outer, 2 if concurrent else 2 * len(loopVariables)
1549
1550 @staticmethod
1551 @debugDecor
1552 def insertInList(pos, item, parent):
1553 """
1554 :param pos: insertion position
1555 :param item: item to add to the list
1556 :param parent: the parent of item (the list)
1557 """
1558 # insertion
1559 if pos < 0:
1560 pos = len(parent) + 1 + pos
1561 parent.insert(pos, item)
1562 if len(parent) > 1:
1563 i = list(parent).index(item) # effective position
1564 if i == len(parent) - 1:
1565 # The item is the last one
1566 parent[i - 1].tail = ', '
1567 else:
1568 parent[i].tail = ', '
1569
1570 @debugDecor
1571 def removeFromList(self, item, itemPar):
1572 """
1573 :param item: item to remove from list
1574 :param itemPar: the parent of item (the list)
1575 """
1576
1577 nodesToSuppress = [item]
1578
1579 # Suppression of the comma
1580 i = list(itemPar).index(item)
1581 if item.tail is not None and ',' in item.tail:
1582 # There's a comma just after the node
1583 tail = item.tail
1584 item.tail = tail.replace(',', '')
1585 elif i != 0 and ',' in itemPar[i - 1].tail:
1586 # There's a comma just before the node
1587 tail = itemPar[i - 1].tail
1588 itemPar[i - 1].tail = tail.replace(',', '')
1589 else:
1590 found = False
1591 # We look for a comma in the first node after the current node that
1592 # is not after another item of the list
1593 j = i + 1
1594 while j < len(itemPar) and not found:
1595 if nonCode(itemPar[j]):
1596 # This is a candidate
1597 if itemPar[j].tail is not None and ',' in itemPar[j].tail:
1598 # Comma found and suppressed
1599 found = True
1600 tail = itemPar[j].tail
1601 itemPar[j].tail = tail.replace(',', '')
1602 else:
1603 j += 1
1604 else:
1605 # This is another item
1606 break
1607
1608 # We look for a comma in the last node before the current node that
1609 # is not a comment or a contiuation character
1610 j = i - 1
1611 while j >= 0 and not found:
1612 if itemPar[j].tail is not None and ',' in itemPar[j].tail:
1613 # Comma found and suppressed
1614 found = True
1615 tail = itemPar[j].tail
1616 itemPar[j].tail = tail.replace(',', '')
1617 else:
1618 if nonCode(itemPar[j]):
1619 # We can search before
1620 j -= 1
1621 else:
1622 # This is another item
1623 break
1624
1625 if not found and \
1626 len([e for e in itemPar if not nonCode(e)]) != 1:
1627 raise RuntimeError("Something went wrong here....")
1628
1629 # Suppression of continuation characters
1630 if i + 1 < len(itemPar) and tag(itemPar[i + 1]) == 'cnt':
1631 # Node is followed by a continuation character
1632 reason = 'lastOnLine'
1633 # If the node is followed by a continuation character and is just after another
1634 # continuation character, we must supress the continuation character which is after.
1635 # USE MODD, ONLY: X, &
1636 # Y, & !Variable to suppress, with its '&' character
1637 # Z
1638 # In addition, if the character found before is at the begining of the line,
1639 # it must also be removed. To know if it is at the begining of the line,
1640 # we can check if another continuation character is before.
1641 # USE MODD, ONLY: X, &
1642 # & Y, & !Variable to suppress, with both '&' characters
1643 # & Z
1644 elif len([itemPar[j] for j in range(i + 1, len(itemPar)) if not nonCode(itemPar[j])]) == 0:
1645 # Node is the last of the list
1646 reason = 'last'
1647 # If the removed node is the last of the list and a continuation character
1648 # is just before. We must suppress it.
1649 # USE MODD, ONLY: X, &
1650 # Y !Variable to suppress, with the preceding '&'
1651 # In addition, if the character found before is at the begining of the line,
1652 # the preceding one must also be removed.
1653 # USE MODD, ONLY: X, &
1654 # & Y !Variable to suppress, with 2 '&'
1655 else:
1656 # We must not suppress '&' characters
1657 reason = None
1658 if reason is not None:
1659 def _getPrecedingCnt(itemPar, i):
1660 """
1661 Return the index of the preceding node which is a continuation character
1662 :param itemPar: the list containig the node to suppress
1663 :param i: the index of the current node, i-1 is the starting index for the search
1664 :return: a tuple with three elements:
1665 - the node containing the preceding '&' character
1666 - the parent of the node containing the preceding '&' character
1667 - index of the preceding '&' in the parent (previsous element
1668 of the tuple)
1669 Note:
1670 - In the general case the preceding '&' belongs to the same list:
1671 USE MODD, ONLY: X, &
1672 Y
1673 - But it exists a special case, where the preceding '&' don't belong to
1674 the same list (in the following example, both '&' are attached to the parent):
1675 USE MODD, ONLY: &
1676 & X
1677 """
1678 j = i - 1
1679 while j >= 0 and tag(itemPar[j]) == 'C':
1680 j -= 1
1681 if j >= 0 and tag(itemPar[j]) == 'cnt':
1682 return itemPar[j], itemPar, j
1683 if j == -1:
1684 # In the following special case, the '&' don't belong to the list but are
1685 # siblings of the list.
1686 # USE MODD, ONLY: &
1687 # & X
1688 siblings = self.getSiblings(itemPar, before=True, after=False)
1689 j2 = len(siblings) - 1
1690 while j2 >= 0 and tag(siblings[j2]) == 'C':
1691 j2 -= 1
1692 if j2 >= 0 and tag(siblings[j2]) == 'cnt':
1693 return siblings[j2], siblings, j2
1694 return None, None, None
1695 # We test if the preceding node (excluding comments) is a continuation character
1696 precCnt, newl, j = _getPrecedingCnt(itemPar, i)
1697 if precCnt is not None:
1698 # Preceding node is a continuation character
1699 nodesToSuppress.append(precCnt if reason == 'last' else itemPar[i + 1])
1700 if j is not None:
1701 precCnt2, _, _ = _getPrecedingCnt(newl, j)
1702 if precCnt2 is not None:
1703 # There is another continuation character before
1704 nodesToSuppress.append(precCnt2 if reason == 'last' else precCnt)
1705
1706 # Suppression of nodes
1707 for node in nodesToSuppress:
1708 # Get the tail of the previous children of the list and append the item's tail
1709 # to be removed
1710 if node in itemPar:
1711 parent = itemPar
1712 else:
1713 # parent must be recomputed because previsous removal may have change it
1714 parent = self.getParent(itemPar)
1715 i = list(parent).index(node)
1716 if i != 0 and node.tail is not None:
1717 if parent[i - 1].tail is None:
1718 parent[i - 1].tail = ''
1719 parent[i - 1].tail = parent[i - 1].tail + node.tail
1720 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