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