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