PyForTool
Python-fortran-tool
Loading...
Searching...
No Matches
variables.py
1"""
2This module implements the VarList and Variables classes to deal with variables
3"""
4
5import logging
6import copy
7import re
8from functools import wraps
9import os
10
11from pyfortool.util import PYFTError, debugDecor, alltext, isExecutable, n2name, tag, noParallel
12from pyfortool.expressions import (createArrayBounds, simplifyExpr, createExprPart,
13 createExpr, createElem)
14from pyfortool.tree import updateTree
16
17
18# No @debugDecor for this low-level method
19def _getDeclStmtTag(scopePath):
20 """
21 Internal function
22 :param scopePath: a scope path
23 :return: the declaration statement we can find in this scope path
24 """
25 if scopePath.split('/')[-1].split(':')[0] == 'type':
26 declStmt = 'component-decl-stmt'
27 else:
28 declStmt = 'T-decl-stmt'
29 return declStmt
30
31
32def updateVarList(func):
33 """
34 Decorator to signal that a varList update is needed
35 """
36 @wraps(func)
37 def wrapper(self, *args, **kwargs):
38 result = func(self, *args, **kwargs)
39 self.mainScope._varList = None # pylint: disable=protected-access
40 return result
41 return wrapper
42
43
44class VarList():
45 """
46 The VarList class stores the characterisitcs of all variables contained in a source code
47 """
48 def __init__(self, mainScope, _preCompute=None):
49 """
50 :param mainScope: a PYFT object
51 :param _preCompute: pre-computed values (_varList, _fullVarList, _scopePath)
52
53 Notes: - variables are found in modules only if the 'ONLY' attribute is used
54 - array specification and type is unknown for module variables
55 - 'ASSOCIATE' statements are not followed
56 """
57 # _varList is the effective list of variables for the current scope
58 # _fullVarList is the list of variables of the whole file
59 # We need it because findVar must be able to return a variable declaration
60 # that exists in an upper scope
61 # _scopePath is the path of the current scope
62
63 if _preCompute is None:
64 self._varList = self._fromScope(mainScope)
65 self._scopePath = mainScope.path
66 self._fullVarList = self._varList
67 else:
68 assert mainScope is None
69 self._varList, self._fullVarList, self._scopePath = _preCompute
70
71 def __getitem__(self, *args, **kwargs):
72 return self._varList.__getitem__(*args, **kwargs)
73
74 def __setitem__(self, *args, **kwargs):
75 return self._varList.__setitem__(*args, **kwargs)
76
77 def __delitem__(self, *args, **kwargs):
78 return self._varList.__delitem__(*args, **kwargs)
79
80 def __len__(self, *args, **kwargs):
81 return self._varList.__len__(*args, **kwargs)
82
83 @staticmethod
84 def _fromScope(mainScope):
85 """
86 :param mainScope: a PYFT object
87 """
88 def decodeArraySpecs(arraySpecs):
89 asList = []
90 asxList = []
91 for arraySpec in arraySpecs:
92 lb = arraySpec.find('.//{*}lower-bound')
93 ub = arraySpec.find('.//{*}upper-bound')
94 asList.append([alltext(lb) if lb is not None else None,
95 alltext(ub) if ub is not None else None])
96 asxList.append([lb if lb is not None else None, ub if ub is not None else None])
97 return asList, asxList
98
99 result = []
100 for scope in mainScope.getScopes():
101 # In case scope is a function, we determine the name of the result
102 if tag(scope[0]) == 'function-stmt':
103 rSpec = scope[0].find('./{*}result-spec/{*}N')
104 funcResultName = rSpec if rSpec is not None else \
105 scope[0].find('./{*}function-N/{*}N')
106 funcResultName = n2name(funcResultName).upper()
107 else:
108 funcResultName = ''
109
110 # Find dummy arguments
111 dummyArgs = [n2name(e).upper() for stmt in scope
112 for e in stmt.findall('.//{*}dummy-arg-LT/{*}arg-N/{*}N')]
113
114 # Loop on each declaration statement
115 for declStmt in [stmt for stmt in scope
116 if tag(stmt) in ('T-decl-stmt' 'component-decl-stmt')]:
117 tSpec = alltext(declStmt.find('.//{*}_T-spec_'))
118 iSpec = declStmt.find('.//{*}intent-spec')
119 if iSpec is not None:
120 iSpec = iSpec.text
121 optSpec = False
122 allocatable = False
123 parameter = False
124 pointer = False
125 allattributes = declStmt.findall('.//{*}attribute/{*}attribute-N')
126 for attribute in allattributes:
127 if alltext(attribute).upper() == 'OPTIONAL':
128 optSpec = True
129 if alltext(attribute).upper() == 'ALLOCATABLE':
130 allocatable = True
131 if alltext(attribute).upper() == 'PARAMETER':
132 parameter = True
133 if alltext(attribute).upper() == 'POINTER':
134 pointer = True
135 # Dimensions declared with the DIMENSION attribute
136 arraySpecs = declStmt.findall('.//{*}attribute//{*}array-spec//{*}shape-spec')
137 as0List, asx0List = decodeArraySpecs(arraySpecs)
138
139 # Loop on each declared variables
140 for enDecl in declStmt.findall('.//{*}EN-decl'):
141 varName = n2name(enDecl.find('.//{*}N')).upper()
142 # Dimensions declared after the variable name
143 arraySpecs = enDecl.findall('.//{*}array-spec//{*}shape-spec')
144 asList, asxList = decodeArraySpecs(arraySpecs)
145 # Initial value (parameter or not)
146 init = enDecl.find('./{*}init-E')
147 if init is not None:
148 init = alltext(init)
149
150 result.append({'as': asList if len(as0List) == 0 else as0List,
151 'asx': asxList if len(asx0List) == 0 else asx0List,
152 'n': varName, 'i': iSpec, 't': tSpec,
153 'arg': varName in dummyArgs,
154 'use': False, 'opt': optSpec, 'allocatable': allocatable,
155 'parameter': parameter, 'pointer': pointer,
156 'result': funcResultName == varName,
157 'init': init, 'scopePath': scope.path})
158
159 # Loop on each use statement
160 for useStmt in [stmt for stmt in scope if tag(stmt) == 'use-stmt']:
161 module = n2name(useStmt.find('.//{*}module-N').find('.//{*}N'))
162 for var in useStmt.findall('.//{*}use-N'):
163 varName = n2name(var.find('.//{*}N'))
164 result.append({'as': None, 'asx': None,
165 'n': varName, 'i': None, 't': None, 'arg': False,
166 'use': module, 'opt': None, 'allocatable': None,
167 'parameter': None, 'pointer': None, 'result': None,
168 'init': None, 'scopePath': scope.path})
169 return result
170
171 def restrict(self, scopePath, excludeContains):
172 """
173 :param scopePath: return a varList restricted to this scope path
174 :param excludeContains: exclude variables declared in contained parts
175 """
176 scopePath = '' if scopePath == '/' else scopePath
177 root = scopePath + '/' if scopePath == '/' else scopePath
178 varList = [item for item in self._varList
179 if item['scopePath'] == scopePath or
180 (item['scopePath'].startswith(root) and
181 not excludeContains)]
182 return VarList(None, _preCompute=(varList, self._fullVarList, scopePath))
183
184 @debugDecor
185 def findVar(self, varName, array=None, exactScope=False, extraVarList=None):
186 """
187 Search for a variable in a list of declared variables
188 :param varName: variable name
189 :param array: True to limit search to arrays,
190 False to limit search to non array,
191 None to return anything
192 :param exactScope: True to limit search to variables declared in the scopePath
193 :param extraVarList: None or list of variables (such as those contained in a VarList object)
194 defined but not yet available in the self.varList object.
195 :return: None if not found or the description of the variable
196
197 The function is designed to return the declaration of a given variable.
198 If we know that the variable is (is not) an array, the last declaration statement
199 must (must not) be an array declaration. If the last declaration statement found doesn't
200 correspond to what is expected, we don't return it.
201 In case array is None, we return the last declaration statement without checking its kind.
202 """
203 extraVarList = extraVarList if extraVarList is not None else []
204 # Select all the variables declared in the current scope or upper,
205 # then select the last declared
206 candidates = {v['scopePath']: v for v in self._fullVarList + extraVarList
207 if v['n'].upper() == varName.upper() and
208 (self._scopePath == v['scopePath'] or
209 (self._scopePath.startswith(v['scopePath'] + '/') and
210 not exactScope))}
211 if len(candidates) > 0:
212 last = candidates[max(candidates, key=len)]
213 if array is True and last.get('as', None) is not None and len(last['as']) > 0:
214 return last
215 if array is False and len(last.get('as', [])) == 0:
216 return last
217 if array is None:
218 return last
219 return None
220
221 @debugDecor
222 def showVarList(self):
223 """
224 Display on stdout a nice view of all the variables
225 """
226 for sc in set(v['scopePath'] for v in self._varList):
227 print(f'List of variables declared in {sc}:')
228 for var in [var for var in self._varList if var['scopePath'] == sc]:
229 print(f" Variable {var['n']}:")
230 if var['use']:
231 print(f" is a variable taken in the {var['use']} module")
232 else:
233 isscalar = len(var['as']) == 0
234 if isscalar:
235 print(' is scalar')
236 else:
237 print(' is of rank {}, with dimensions {}'.format(len(var['as']),
238 ', '.join([(':'.join([('' if s is None else s)
239 for s in var['as'][i]]))
240 for i in range(len(var['as']))])))
241 if var['arg']:
242 intent = 'without intent' if var['i'] is None else \
243 f"with intent {var['i']}"
244 print(f' is a dummy argument {intent}')
245 else:
246 print(' is a local variable')
247 print()
248
249
250class Variables():
251 """
252 Methos to deal with variables
253 """
254 def __init__(self, **kwargs): # pylint: disable=unused-argument
255 """
256 **kwargs is used to enable the use of super().__init__
257 """
258 self._varList = None
259
260 @property
261 def varList(self):
262 """
263 Returns the varList object corresponding to the node
264 """
265 # Evaluate the varList object if not already done
266 if self.mainScope._varList is None: # pylint: disable=protected-access
267 self.mainScope._varList = VarList(self.mainScope) # pylint: disable=protected-access
268
269 # Restrict the object to the current node
270 # pylint: disable-next=protected-access
271 return self.mainScope._varList.restrict(self.path, self._excludeContains)
272
273 # No @debugDecor for this low-level method
274 def _normalizeScopeVar(self, scopeVarList):
275 """
276 Internal method to normalize scopeVarList
277 (list of tuples made of scope path, variable name, and optional other values)
278 """
279 return [(self.normalizeScope(scopePath), var.upper(), *other)
280 for (scopePath, var, *other) in scopeVarList]
281
282 # No @debugDecor for this low-level method
283 def _normalizeUniqVar(self, scopeVarList):
284 """
285 Internal method to suppress duplicates in scopeVarList
286 (list of tuples made of scope path, variable name, and optional other values)
287 """
288 # We could use list(set(self._normalizeScopeVar(scopeVarList)))
289 # but order differs from one execution to the other
290 result = []
291 for scopeVar in self._normalizeScopeVar(scopeVarList):
292 if scopeVar not in result:
293 result.append(scopeVar)
294 return result
295
296 @debugDecor
298 """
299 Find all T-decl-stmt elements that have a child element 'attribute' with
300 attribute-N="DIMENSION" and move the attribute into EN-N elements
301 E.g., before :
302 REAL, DIMENSION(D%NIJT,D%NKT) :: ZTLK, ZRT
303 INTEGER, PARAMETER, DIMENSION(1,1) :: IBUEXTRAIND=(/18, 30/)
304 after :
305 REAL :: ZTLK(D%NIJT,D%NKT), ZRT(D%NIJT,D%NKT)
306 INTEGER, PARAMETER :: IBUEXTRAIND(1,1)=(/18, 30/)
307 Limitations : "DIMENSION" must be in upper case in attribute.text
308 """
309 # Find all T-decl-stmt elements that have a child element 'attribute'
310 # with attribute-N="DIMENSION"
311 for decl in self.findall('.//{*}T-decl-stmt'):
312 arraySpec = decl.find('./{*}attribute[{*}attribute-N="DIMENSION"]/{*}array-spec')
313 attrElem = decl.find('./{*}attribute[{*}attribute-N="DIMENSION"]/{*}array-spec/...')
314 # Discard allocatable (':' in arraySpec)
315 if arraySpec is not None and ':' not in alltext(arraySpec):
316 # Check if EN-decl elements don't already have an array-spec child element
317 # or an intial value
318 if decl.find('./{*}EN-decl-LT/{*}EN-decl/{*}array-spec') is None and \
319 decl.find('./{*}EN-decl-LT/{*}EN-decl/{*}init-E') is None:
320 # Attach the array-spec element after the EN-N element
321 for elem in decl.findall('./{*}EN-decl-LT/{*}EN-decl'):
322 elem.append(copy.deepcopy(arraySpec))
323 # Remove the dimension and array-spec elements
324 self.removeFromList(attrElem, decl)
325
326 @debugDecor
327 def checkImplicitNone(self, mustRaise=False):
328 """
329 :param mustRaise: True to raise
330 Issue a logging.warning if the "IMPLICIT NONE" statment is missing
331 If mustRaise is True, issue a logging.error instead and raise an error
332 """
333 for scope in self.getScopes():
334 # The IMPLICIT NONE statement is inherited from the top unit, control at top
335 # unit is enough apart for INTERFACE blocs
336 if (scope.path.count('/') == 0 or
337 (scope.path.count('/') >= 2 and
338 scope.path.split('/')[-2].startswith('interface:'))):
339 if scope.find('./{*}implicit-none-stmt') is None:
340 message = "The 'IMPLICIT NONE' statment is missing in file " + \
341 "'{file}' for {scopePath}.".format(file=scope.getFileName(),
342 scopePath=scope.path)
343 if mustRaise:
344 logging.error(message)
345 raise PYFTError(message)
346 logging.warning(message)
347
348 @debugDecor
349 def checkIntent(self, mustRaise=False):
350 """
351 :param mustRaise: True to raise
352 Issue a logging.warning if some "INTENT" attributes are missing
353 If mustRaise is True, issue a logging.error instead and raise an error
354 """
355 ok = True
356 log = logging.error if mustRaise else logging.warning
357 for var in self.varList:
358 if var['arg'] and var['i'] is None:
359 log(("The dummy argument {} as no INTENT attribute, in " +
360 "file '{}'").format(var['n'], self.getFileName()))
361 ok = False
362 if not ok and mustRaise:
363 raise PYFTError("There are dummy arguments without INTENT attribute in " +
364 "file '{}'".format(self.getFileName()))
365
366 @debugDecor
367 @noParallel
368 @updateVarList
369 @updateTree('signal')
370 def removeVar(self, varList, simplify=False):
371 """
372 :param varList: list of variables to remove. Each item is a list or tuple of two elements.
373 The first one describes where the variable is used, the second one is
374 the name of the variable. The first element is a '/'-separated path with
375 each element having the form 'module:<name of the module>',
376 'sub:<name of the subroutine>', 'func:<name of the function>' or
377 'type:<name of the type>'
378 :param simplify: try to simplify code (if we delete a declaration statement that used a
379 variable as kind selector, and if this variable is not used else where,
380 we also delete it)
381 Remove the variable from declaration, and from the argument list if needed
382 """
383 varList = self._normalizeUniqVar(varList)
384
385 # Sort scopes by depth
386 sortedVarList = {}
387 for scopePath, varName in varList:
388 nb = scopePath.count('/')
389 sortedVarList[nb] = sortedVarList.get(nb, []) + [(scopePath, varName.upper())]
390
391 varToRemoveIfUnused = []
392 # Loop on varList starting by inner most variables
393 nbList = [] if len(sortedVarList.keys()) == 0 else \
394 range(max(sortedVarList.keys()) + 1)[::-1]
395 for nb in nbList:
396 sortedVarList[nb] = sortedVarList.get(nb, [])
397 # Loop on scopes
398 for scopePath in list(set(scopePath for scopePath, _ in sortedVarList[nb])):
399 # use of mainScope because variable can be declared upper than self
400 scope = self.mainScope.getScopeNode(scopePath)
401 # Variables searched in this scope
402 varNames = list(set(v for (w, v) in sortedVarList[nb] if w == scopePath))
403 declStmt = _getDeclStmtTag(scopePath)
404 # If scopePath is "module:XX/sub:YY", getScopeNode returns a node
405 # containing the subroutine declaration statements and
406 # excluding the subroutine and functions potentially included
407 # after a "contains" statement
408 previous = None
409 # list() to allow removing during the iteration
410 for node in list(scope):
411 deleted = False
412
413 # Checks if variable is a dummy argument
414 dummyList = node.find('{*}dummy-arg-LT') # This is the list of the dummies
415 if dummyList is not None:
416 # Loop over all dummy arguments
417 for arg in dummyList.findall('.//{*}arg-N'):
418 name = n2name(arg.find('.//{*}N')).upper()
419 for varName in [v for v in varNames if v == name]:
420 # This dummy arg is a searched variable, we remove it from the list
421 scope.removeFromList(arg, dummyList)
422
423 # In case the variable is declared
424 if tag(node) == declStmt:
425 # We are in a declaration statement
426 # list of declaration in the current statment
427 declList = node.find('./{*}EN-decl-LT')
428 for enDecl in declList.findall('.//{*}EN-decl'):
429 name = n2name(enDecl.find('.//{*}N')).upper()
430 for varName in [v for v in varNames if v == name]:
431 # The argument is declared here,
432 # we suppress it from the declaration list
433 varNames.remove(varName)
434 scope.removeFromList(enDecl, declList)
435 # In all the variables are suppressed from the declaration statement
436 if len(list(declList.findall('./{*}EN-decl'))) == 0:
437 if simplify:
438 varToRemoveIfUnused.extend([[scopePath, n2name(nodeN)]
439 for nodeN in node.findall('.//{*}N')])
440 # We will delete the current node but we don't want to lose
441 # any text. So, we put the node's text in the tail of the previous node
442 if previous is not None and node.tail is not None:
443 if previous.tail is None:
444 previous.tail = ''
445 previous.tail += node.tail
446 deleted = True
447 scope.getParent(node).remove(node)
448
449 # In case the variable is a module variable
450 if tag(node) == 'use-stmt':
451 # We are in a use statement
452 useList = node.find('./{*}rename-LT')
453 if useList is not None:
454 for rename in useList.findall('.//{*}rename'):
455 name = n2name(rename.find('.//{*}N')).upper()
456 for varName in [v for v in varNames if v == name]:
457 varNames.remove(varName)
458 # The variable is declared here, we remove it from the list
459 scope.removeFromList(rename, useList)
460 # In case the variable was alone
461 attribute = node.find('{*}module-N').tail
462 if attribute is None:
463 attribute = ''
464 attribute = attribute.replace(' ', '').replace('\n', '')
465 attribute = attribute.replace('&', '').upper()
466 useList = node.find('./{*}rename-LT')
467 if len(useList) == 0 and attribute[0] == ',' and \
468 attribute[1:] == 'ONLY:':
469 # If there is a 'ONLY' attribute,
470 # we suppress the use statement entirely
471 if previous is not None and node.tail is not None:
472 if previous.tail is None:
473 previous.tail = ''
474 previous.tail += node.tail
475 deleted = True
476 scope.getParent(node).remove(node)
477 scope.tree.signal(scope) # Tree must be updated
478 elif len(useList) == 0:
479 # there is no 'ONLY' attribute
480 moduleName = scope.getSiblings(useList, before=True,
481 after=False)[-1]
482 previousTail = moduleName.tail
483 if previousTail is not None:
484 moduleName.tail = previousTail.replace(',', '')
485 scope.getParent(useList).remove(useList)
486 # end loop if all variables have been found
487 if len(varNames) == 0:
488 break
489 # Store node for the following iteration
490 if not deleted:
491 previous = node
492
493 # If some variables have not been found, they are certainly declared one level upper
494 if len(varNames) != 0:
495 newWhere = '/'.join(scopePath.split('/')[:-1])
496 sortedVarList[nb - 1] = sortedVarList.get(nb - 1, []) + \
497 [(newWhere, varName) for varName in varNames]
498
499 if simplify and len(varToRemoveIfUnused) > 0:
500 self.removeVarIfUnused(varToRemoveIfUnused, excludeDummy=True, simplify=True)
501
502 @debugDecor
503 @updateVarList
504 def addVar(self, varList):
505 """
506 :param varList: list of variable specification to insert in the xml code
507 a variable specification is a list of four elements:
508 - variable scope path (path to module, subroutine, function or type
509 declaration)
510 - variable name
511 - declarative statment
512 - position of the variable in the list of dummy argument,
513 None for a local variable
514 """
515 varList = self._normalizeUniqVar(varList)
516
517 for (scopePath, name, declStmt, pos) in varList:
518 scope = self.getScopeNode(scopePath)
519
520 # Add variable to the argument list
521 if pos is not None:
522 argN = createElem('arg-N')
523 nodeN = createElem('N')
524 nodeN.append(createElem('n', text=name))
525 argN.append(nodeN)
526 # search for a potential node, within the scope, with a list of dummy arguments
527 argLst = scope.find('.//{*}dummy-arg-LT')
528 if argLst is None:
529 # This was a subroutine or function without dummy arguments
530 scope[0][0].tail = '('
531 argLst = createElem('dummy-arg-LT', tail=')')
532 scope[0].insert(1, argLst)
533 scope.insertInList(pos, argN, argLst)
534
535 # Declare the variable
536 # The following test is needed in case several variables are added in the argument list
537 # but the declaration statement is given only once for all the variables
538 if declStmt is not None and declStmt != '':
539 # Declaration statement tag according to path (member of type declaration or not)
540 declStmtTag = _getDeclStmtTag(scopePath)
541
542 if scopePath.split('/')[-1].split(':')[0] == 'type':
543 # Add declaration statement in type declaration
544 # Statement building
545 ds = createExpr(declStmt)[0]
546 previousTail = '\n' + declStmt[:re.search(r'\S', declStmt).start()]
547
548 # node insertion
549 # scope[0] is the T-stmt node, scope[-1] is the end-T-stmt node
550 # scope[-2] is the last node before the end-T-stmt node (last component,
551 # comment or the T-stmt node)
552 ds.tail = scope[-2].tail
553 scope[-2].tail = previousTail
554 scope.insert(-1, ds) # insert before last one
555
556 else:
557 # Add declaration statement (not type declaration case)
558 # Statement building
559 ds = createExpr(declStmt)[0]
560 previousTail = '\n' + declStmt[:re.search(r'\S', declStmt).start()]
561
562 # node insertion index
563 declLst = [node for node in scope if tag(node) == declStmtTag]
564 if len(declLst) != 0:
565 # There already are declaration statements
566 # We look for the last position in the declaration list which do not use
567 # the variable we add
568 for decl in declLst:
569 index = list(scope).index(decl)
570 if name in [n2name(nodeN) for nodeN in decl.findall('.//{*}N')]:
571 break
572 else:
573 # There is no declaration statement
574 # list of executable nodes
575 stmtLst = [node for node in scope if isExecutable(node)]
576 if len(stmtLst) == 0:
577 # There is no executable statement, we insert the declaration at the end
578 # Last node is the ending node (e.g. end-subroutine-stmt)
579 index = len(scope) - 1
580 else:
581 # We insert the declaration just before the first executable statement
582 index = list(scope).index(stmtLst[0])
583
584 # node insertion
585 if index != 0:
586 ds.tail = scope[index - 1].tail
587 scope[index - 1].tail = previousTail
588 scope.insert(index, ds)
589
590 @debugDecor
591 @noParallel
592 @updateVarList
593 @updateTree('signal')
594 def addModuleVar(self, moduleVarList):
595 """
596 :param moduleVarList: list of module variable specification to insert in the xml code
597 a module variable specification is a list of three elements:
598 - scope path (path to module, subroutine, function or type
599 declaration)
600 - module name
601 - variable name or or list of variable names
602 or None to add a USE statement without the ONLY attribute
603 For example addModuleVar('sub:FOO', 'MODD_XX', 'Y') will add the following line in
604 subroutine FOO:
605 USE MODD_XX, ONLY: Y
606 """
607 moduleVarList = self._normalizeScopeVar(moduleVarList)
608
609 for (scopePath, moduleName, varName) in moduleVarList:
610 if varName is None:
611 varName = []
612 elif not isinstance(varName, list):
613 varName = [varName]
614 scope = self.getScopeNode(scopePath)
615
616 # USE statement already present
617 useLst = [node for node in scope if tag(node) == 'use-stmt']
618
619 # Check if we need to add a USE
620 insertUse = True
621 for us in useLst:
622 usName = n2name(us.find('.//{*}module-N//{*}N'))
623 usVar = [n2name(v.find('.//{*}N')).upper() for v in us.findall('.//{*}use-N')]
624 if len(varName) == 0 and len(usVar) == 0 and usName.upper() == moduleName.upper():
625 # There aleardy is a 'USE MODULE' and we wanted to insert a 'USE MODULE'
626 insertUse = False
627 elif len(varName) > 0 and len(usVar) > 0 and usName.upper() == moduleName.upper():
628 # There already is a 'USE MODULE, ONLY:' and we want to insert another
629 # 'USE MODULE, ONLY:'
630 # We suppress from the varName list, all the variables already defined
631 varName = [var for var in varName if var.upper() not in usVar]
632 if len(varName) == 0:
633 # All the variables we wanted to import are already defined
634 insertUse = False
635
636 if insertUse:
637 # Statement building
638 stmt = f'USE {moduleName}'
639 if len(varName) > 0:
640 stmt += ', ONLY:{}'.format(', '.join(varName))
641 us = createExpr(stmt)[0]
642
643 # node insertion index
644 if len(useLst) != 0:
645 # There already have use statements, we add the new one after them
646 index = list(scope).index(useLst[-1]) + 1
647 else:
648 # There is no use statement, we add the new node just after the first node
649 index = 1
650
651 us.tail = scope[index - 1].tail
652 scope[index - 1].tail = '\n'
653 scope.insert(index, us)
654 scope.tree.signal(scope) # Tree must be updated
655
656 @debugDecor
657 def showUnusedVar(self):
658 """
659 Displays on stdout a list of unused variables
660 """
661 scopes = self.getScopes(excludeKinds=['type'])
662 varUsed = self.isVarUsed([(scope.path, v['n'])
663 for scope in scopes
664 for v in self.varList
665 if v['scopePath'] == scope.path])
666 for scope in scopes:
667 varList = [k[1].upper() for (k, v) in varUsed.items() if (not v) and k[0] == scope.path]
668 if len(varList) != 0:
669 print(f'Some variables declared in {scope.path} are unused:')
670 print(' - ' + ('\n - '.join(varList)))
671
672 @debugDecor
673 def checkUnusedLocalVar(self, mustRaise=False, excludeList=None):
674 """
675 :param mustRaise: True to raise
676 :param excludeList: list of variable names to exclude from the check
677 Issue a logging.warning if there are unused local variables
678 If mustRaise is True, issue a logging.error instead and raise an error
679 """
680
681 if excludeList is None:
682 excludeList = []
683 else:
684 excludeList = [v.upper() for v in excludeList]
685 scopes = self.getScopes(excludeKinds=['type'])
686 # We do not check dummy args, module variables
687 varUsed = self.isVarUsed([(scope.path, v['n'])
688 for scope in scopes
689 for v in self.varList
690 if (v['n'].upper() not in excludeList and
691 (not v['arg']) and
692 v['scopePath'].split('/')[-1].split(':')[0] != 'module' and
693 v['scopePath'] == scope.path)])
694 for scope in scopes:
695 for var in [k[1].upper() for (k, v) in varUsed.items()
696 if (not v) and k[0] == scope.path]:
697 message = f"The {var} variable is not used in file " + \
698 f"'{scope.getFileName()}' for {scope.path}."
699 if mustRaise:
700 logging.error(message)
701 raise PYFTError(message)
702 logging.warning(message)
703
704 @debugDecor
705 def removeUnusedLocalVar(self, excludeList=None, simplify=False):
706 """
707 Remove unused local variables (dummy and module variables are not suppressed)
708 :param excludeList: list of variable names to exclude from removal (even if unused)
709 :param simplify: try to simplify code (if we delete a declaration statement that used a
710 variable as kind selector, and if this variable is not used else where,
711 we also delete it)
712 """
713 if excludeList is None:
714 excludeList = []
715 else:
716 excludeList = [item.upper() for item in excludeList]
717
718 allVar = [(scope.path, v['n'])
719 for scope in self.getScopes(excludeKinds=['type'])
720 for v in scope.varList
721 if v['n'].upper() not in excludeList and v['scopePath'] == scope.path]
722 self.removeVarIfUnused(allVar, excludeDummy=True, excludeModule=True, simplify=simplify)
723
724 @debugDecor
725 def addExplicitArrayBounds(self, node=None):
726 """
727 Replace ':' by explicit arrays bounds.
728 :param node: xml node in which ':' must be replaced (None to replace everywhere)
729 """
730 if node is None:
731 nodes = [(scope, scope) for scope in self.getScopes()]
732 else:
733 nodes = [(self, node)]
734
735 for (scope, childNode) in nodes:
736 for parent4 in [parent4 for parent4
737 in childNode.findall('.//{*}section-subscript/../../../..')
738 if parent4.find('./{*}R-LT/{*}component-R') is None]:
739 # Shape of type members is unknown
740 for parent in parent4.findall('.//{*}section-subscript/..'):
741 for sub in parent.findall('.//{*}section-subscript'):
742 lowerUsed = sub.find('./{*}lower-bound')
743 upperUsed = sub.find('./{*}upper-bound')
744 # A slice can be A(:), A(I:) or A(:I), but not A(I)
745 # With A(:) and A(:I), lowerUsed is None
746 # With A(I:) lowerUsed.tail contains a ':'
747 # With A(I) lowerUsed.tail doesn't contain a ':'
748 if lowerUsed is None or \
749 (lowerUsed.tail is not None and ':' in lowerUsed.tail):
750 if lowerUsed is None or upperUsed is None:
751 # At least one array bound is implicit
752 varDesc = scope.varList.findVar(n2name(parent4.find('.//{*}N')))
753 if varDesc is not None and varDesc['t'] is not None and \
754 'CHAR' not in varDesc['t']: # module array or character
755 lowerDecl, upperDecl = varDesc['as'][list(parent).index(sub)]
756 if lowerDecl is None:
757 lowerDecl = '1'
758 # When a bound is explicit, we keep it, otherwise we
759 # take the declared bound
760 lowerBound = lowerDecl if lowerUsed is None \
761 else alltext(lowerUsed)
762 upperBound = upperDecl if upperUsed is None \
763 else alltext(upperUsed)
764 if upperBound is not None: # case of implicit shape
765 lowerXml, upperXml = createArrayBounds(lowerBound,
766 upperBound,
767 'ARRAY')
768 # We remove current explicit bounds or the ':',
769 # and replace them by the new explicit declaration
770 for nnn in sub:
771 if tag(nnn) in ('lower-bound', 'upper-bound'):
772 sub.remove(nnn)
773 else:
774 raise PYFTError("Unexpected case, " +
775 "tag is {}".format(tag(nnn)))
776 sub.text = '' # Delete the initial ':'
777 sub.extend([lowerXml, upperXml])
778
779 @debugDecor
780 @noParallel
782 """
783 Look for arrays and add parenthesis. A => A(:)
784 """
785 # Loop on scopes
786 for scope in self.getScopes():
787 for node in scope.iter():
788 # Arrays are used in statements
789 # * We must exclude allocate-stmt, deallocate-stmt, pointer-a-stmt, T-decl-stmt and
790 # associate-stmt,
791 # nullify-stmt function-stmt, subroutine-stmt, interface-stmt must be kept
792 # untouched
793 # action-stmt is discarded as it contains another statement.
794 # * Array variables to modify can be found in
795 # - simple affectation (a-stmt),
796 # - if construct conditions (if-then-stmt, else-if-stmt),
797 # - where-construct mask (where-construct-stmt, else-where-stmt),
798 # - if statement condition (if-stmt/condition-E, be carefull to not take the
799 # whole if-stmt as it contains the action-stmt which,
800 # in turn, can contain an allocate/deallocate/pointer
801 # assignment)
802 # - where statement mask (where-stmt/mask-E and not directly
803 # where-stmt as for if-stmt),
804 # - select-case-stmt and case-stmt,
805 # - do-stmt or forall-construct-stmt (e.g. FOR I=LBOUND(A, 0), UBOUND(A, 0))
806 # - forall-stmt/forall-triplet-spec-LT
807 nodeToTransform = None
808 if tag(node) in ('allocate-stmt', 'deallocate-stmt', 'pointer-a-stmt',
809 'T-decl-stmt', 'associate-stmt', 'function-stmt',
810 'subroutine-stmt', 'interface-stmt', 'action-stmt',
811 'nullify-stmt'):
812 # excluded
813 pass
814 elif tag(node) in ('if-stmt', 'where-stmt', 'forall-stmt'):
815 # We must transform only a part of the node
816 part = {'if-stmt': 'condition-E', 'where-stmt': 'mask-E',
817 'forall-stmt': 'forall-triplet-spec-LT'}[tag(node)]
818 nodeToTransform = node.find('./{*}' + part)
819 elif tag(node).endswith('-stmt'):
820 nodeToTransform = node
821 if nodeToTransform is not None:
822 scope.addArrayParenthesesInNode(nodeToTransform)
823
824 @debugDecor
826 """
827 Look for arrays and add parenthesis. A => A(:)
828 :param node: xml node in which ':' must be added
829 """
830 # Loop on variables
831 for namedE in node.findall('.//{*}named-E'):
832 if not namedE.find('./{*}R-LT'): # no parentheses
833 if not self.isNodeInProcedure(namedE, ('ALLOCATED', 'ASSOCIATED', 'PRESENT')):
834 # Pointer/allocatable used in ALLOCATED/ASSOCIATED must not be modified
835 # Array in present must not be modified
836 nodeN = namedE.find('./{*}N')
837 var = self.varList.findVar(n2name(nodeN))
838 if var is not None and var['as'] is not None and len(var['as']) > 0 and \
839 not ((var['pointer'] or var['allocatable']) and self.isNodeInCall(namedE)):
840 # This is a known array variable, with no parentheses
841 # But we exclude pointer allocatable in call statement because the called
842 # subroutine can wait for a pointer/allocatable and not an array (and it
843 # is difficult to guess as it would need to find the source code of the
844 # subroutine)
845 nodeRLT = createElem('R-LT')
846 namedE.insert(list(namedE).index(nodeN) + 1, nodeRLT)
847 arrayR = createElem('array-R', text='(')
848 nodeRLT.append(arrayR)
849 sectionSubscriptLT = createElem('section-subscript-LT', tail=')')
850 arrayR.append(sectionSubscriptLT)
851 for _ in var['as']:
852 sectionSubscript = createElem('section-subscript', text=':', tail=', ')
853 sectionSubscriptLT.append(sectionSubscript)
854 sectionSubscript.tail = None # last one
855
856 @debugDecor
858 """
859 Look for arrays and remove parenthesis if no index selection A(:,:) => A
860 :param node: xml node in which ':' must be removed
861 """
862 # Loop on variables
863 for namedE in node.findall('.//{*}named-E'):
864 if namedE.find('./{*}R-LT'): # parentheses
865 if not self.isNodeInProcedure(namedE, ('ALLOCATED', 'ASSOCIATED', 'PRESENT')):
866 # Pointer/allocatable used in ALLOCATED/ASSOCIATED must not be modified
867 # Array in present must not be modified
868 nodeN = namedE.find('./{*}N')
869 var = self.varList.findVar(n2name(nodeN))
870 if var is not None and var['as'] is not None and len(var['as']) > 0 and \
871 not ((var['pointer'] or var['allocatable']) and self.isNodeInCall(namedE)):
872 arrayR = namedE.findall('./{*}R-LT')
873 for a in arrayR:
874 sectionSubscriptLT = a.findall('.//{*}section-subscript-LT')
875 for ss in sectionSubscriptLT:
876 lowerBound = ss.findall('.//{*}lower-bound')
877 if len(lowerBound) == 0:
878 # Node to be removed <f:R-LT><f:array-R><f:section-subscript-LT>
879 par = self.getParent(ss, level=2)
880 parOfpar = self.getParent(par)
881 parOfpar.remove(par)
882
883 @debugDecor
884 @updateVarList
885 def modifyAutomaticArrays(self, declTemplate=None, startTemplate=None, endTemplate=None):
886 """
887 :param declTemplate: declaration template
888 :param startTemplate: template for the first executable statement
889 :param: endTemplate: template for the last executable statement
890 :return: number of arrays modified
891 Modifies all automatic arrays declaration in subroutine and functions. The declaration is
892 replaced by the declaration template, the start template is inserted as first executable
893 statement and the end template as last executable statement. Each template can use the
894 following place holders:
895 "{doubledotshape}", "{shape}", "{lowUpList}", "{name}" and "{type}" wich are, respectively
896 modified into ":, :, :", "I, I:J, 0:I", "1, I, I, J, 0, I", "A", "REAL" if the original
897 declaration statement was "A(I, I:J, 0:I)". The template
898 "{type}, DIMENSION({doubledotshape}), ALLOCATABLE :: {name}#
899 ALLOCATE({name}({shape}))#DEALLOCATE({name})"
900 replaces automatic arrays by allocatables
901 """
902 templates = {'decl': declTemplate if declTemplate is not None else '',
903 'start': startTemplate if startTemplate is not None else '',
904 'end': endTemplate if endTemplate is not None else ''} # ordered dict
905
906 number = 0
907 for scope in [scope for scope in self.getScopes()
908 if scope.path.split('/')[-1].split(':')[0] in ('sub', 'func')]:
909 # For all subroutine and function scopes
910 # Determine the list of variables to transform
911 varListToTransform = []
912 for var in [var for var in scope.varList
913 if var['as'] is not None and
914 len(var['as']) > 0 and
915 not (var['arg'] or var['allocatable'] or
916 var['pointer'] or var['result'])]:
917 # For all automatic arrays, which are not argument, not allocatable,
918 # not pointer and not result
919 if var['init'] is not None:
920 logging.warning("An array (%s) has an initial value, it can't be " +
921 "processed by modifyAutomaticArrays.)", var['n'])
922 else:
923 varListToTransform.append(var)
924 # A variable can make use of the size of another variable in its declaration statement
925 # We order the variables to not insert the declaration of a variable before the
926 # declaration of the variables it depends on
927 orderedVarListToTransform = []
928 while len(varListToTransform) > 0:
929 nAdded = 0
930 for var in varListToTransform[:]:
931 # flatten var['asx'] excluding None
932 listN = [x for dim in var['asx'] for x in dim if x is not None]
933 listN = [n2name(nodeN).upper() for asx in listN
934 for nodeN in asx.findall('.//{*}N/{*}n/..')]
935 if len(set(listN).intersection([v['n'].upper()
936 for v in varListToTransform])) == 0:
937 # Variable var does not use variables still in varListToTransform
938 varListToTransform.remove(var)
939 orderedVarListToTransform.append(var)
940 nAdded += 1
941 if nAdded == 0:
942 raise PYFTError('It seems that there is a circular reference in ' +
943 'the declaration statements')
944 # Loop on variable to transform
945 for var in orderedVarListToTransform[::-1]: # reverse order
946 number += 1
947 # Apply the template
948 templ = copy.deepcopy(templates)
949 for templPart in templ:
950 if '{doubledotshape}' in templ[templPart]:
951 templ[templPart] = templ[templPart].replace(
952 '{doubledotshape}', ','.join([':'] * len(var['as'])))
953 if '{shape}' in templ[templPart]:
954 result = []
955 for i in range(len(var['as'])):
956 if var['as'][i][0] is None:
957 result.append(var['as'][i][1])
958 else:
959 result.append(var['as'][i][0] + ':' + var['as'][i][1])
960 templ[templPart] = templ[templPart].replace('{shape}', ', '.join(result))
961 if '{name}' in templ[templPart]:
962 templ[templPart] = templ[templPart].replace('{name}', var['n'])
963 if '{type}' in templ[templPart]:
964 templ[templPart] = templ[templPart].replace('{type}', var['t'])
965 if '{lowUpList}' in templ[templPart]:
966 result = []
967 for i in range(len(var['as'])):
968 if var['as'][i][0] is None:
969 result.extend([1, var['as'][i][1]])
970 else:
971 result.extend([var['as'][i][0], var['as'][i][1]])
972 templ[templPart] = templ[templPart].replace(
973 '{lowUpList}', ', '.join([str(r) for r in result]))
974
975 # Get the xml for the template
976 separator = "!ABCDEFGHIJKLMNOPQRSTUVWabcdefghijklmnopqrstuvwxyz0123456789"
977 part = 0
978 for node in createExpr(templ['decl'] + '\n' + separator + '\n' +
979 templ['start'] + '\n' + separator + '\n' +
980 templ['end']):
981 templPart = list(templ.keys())[part]
982 if not isinstance(templ[templPart], list):
983 templ[templPart] = []
984 if tag(node) == 'C' and node.text == separator:
985 part += 1
986 else:
987 templ[templPart].append(node)
988
989 # Replace declaration statement
990 # We look for the last position in the declaration list which do not use the
991 # variable we add
992 # This algorithm will stop when the original declaration statement is encoutered
993 for decl in scope.findall('./{*}T-decl-stmt'):
994 index = list(scope).index(decl)
995 if var['n'] in [n2name(nodeN) for nodeN in decl.findall('.//{*}N')]:
996 break
997 scope.removeVar([(scope.path, var['n'])], simplify=False)
998 for nnn in templ['decl'][::-1]:
999 scope.insert(index, nnn)
1000
1001 # Insert statements
1002 for nnn in templ['start'][::-1]:
1003 scope.insertStatement(nnn, True)
1004 for nnn in templ['end'][::-1]:
1005 scope.insertStatement(nnn, False)
1006 return number
1007
1008 @staticmethod
1009 @debugDecor
1010 def varSpec2stmt(varSpec):
1011 """
1012 :param varSpec: a variable description, same form as the items return by self.varList
1013 :return: the associated declarative statement
1014 """
1015 if varSpec['use'] is not False:
1016 stmt = f"USE {varSpec['use']}, ONLY: {varSpec['n']}"
1017 else:
1018 stmt = varSpec['t']
1019 if varSpec['as']:
1020 stmt += ', DIMENSION('
1021 dl = []
1022 for el in varSpec['as']:
1023 if el[0] is None and el[1] is None:
1024 dl.append(':')
1025 elif el[0] is None:
1026 dl.append(el[1])
1027 else:
1028 dl.append(el[0] + ':' + el[1])
1029 if (varSpec['allocatable'] and not all(d == ':' for d in dl)) or \
1030 (any(d == ':' for d in dl) and not varSpec['allocatable']):
1031 raise PYFTError('Missing dim are mandatory and allowed ' +
1032 'only for allocatable arrays')
1033 stmt += ', '.join(dl) + ')'
1034 if varSpec['allocatable']:
1035 stmt += ", ALLOCATABLE"
1036 if varSpec['parameter']:
1037 stmt += ", PARAMETER"
1038 if varSpec['i'] is not None:
1039 stmt += ", INTENT(" + varSpec['i'] + ")"
1040 if varSpec['opt'] is True:
1041 stmt += ", OPTIONAL"
1042 stmt += " :: " + varSpec['n']
1043 if varSpec['init'] is not None:
1044 stmt += "=" + varSpec['init']
1045 return stmt
1046
1047 @debugDecor
1048 def findIndexArrayBounds(self, arr, index, loopVar):
1049 """
1050 Find bounds and loop variable for a given array index
1051 :param arr: array node (named-E node with a array-R child)
1052 :param index: index of the rank of the array
1053 :param loopVar: None to create new variable for each added DO loop
1054 or a function that return the name of the variable to use for the
1055 loop control.
1056 This function returns a string (name of the variable), or True to create
1057 a new variable, or False to not transform this statement
1058 The functions takes as arguments:
1059 - lower and upper bounds as defined in the declaration statement
1060 - lower and upper bounds as given in the statement
1061 - name of the array
1062 - index of the rank
1063 :return: the tuple (loopName, lowerBound, upperBound) where:
1064 loopName is the name of the variable to use for the loop
1065 lower and upper bounds are the bounds to use for the DO loop
1066 loopName can be:
1067 - a string
1068 - False to discard this index
1069 - True to create a new variable to loop with
1070 """
1071 name = n2name(arr.find('./{*}N'))
1072 ss = arr.findall('./{*}R-LT/{*}array-R/{*}section-subscript-LT/{*}section-subscript')[index]
1073 # We are only interested by the subscript containing ':'
1074 # we must not iterate over the others, eg: X(:,1)
1075 if ':' in alltext(ss):
1076 # Look for lower and upper bounds for iteration and declaration
1077 lowerUsed = ss.find('./{*}lower-bound')
1078 upperUsed = ss.find('./{*}upper-bound')
1079 varDesc = self.varList.findVar(name, array=True)
1080 if varDesc is not None:
1081 lowerDecl, upperDecl = varDesc['as'][index]
1082 if lowerDecl is None:
1083 lowerDecl = '1' # default lower index for FORTRAN arrays
1084 else:
1085 lowerDecl, upperDecl = None, None
1086
1087 # name of the loop variable
1088 if loopVar is None:
1089 # loopVar is not defined, we create a new variable for the loop only if lower
1090 # and upper bounds have been found (easy way to discard character strings)
1091 varName = lowerDecl is not None and upperDecl is not None
1092 else:
1093 varName = loopVar(lowerDecl, upperDecl,
1094 None if lowerUsed is None else alltext(lowerUsed),
1095 None if upperUsed is None else alltext(upperUsed), name, index)
1096 return (varName,
1097 lowerDecl if lowerUsed is None else alltext(lowerUsed),
1098 upperDecl if upperUsed is None else alltext(upperUsed))
1099 return None
1100
1101 @debugDecor
1102 def arrayR2parensR(self, namedE, table):
1103 """
1104 Transform a array-R into a parens-R node by replacing slices by variables
1105 In 'A(:)', the ':' is in a array-R node whereas in 'A(JL)', 'JL' is in a parens-R node.
1106 Both the array-R and the parens-R nodes are inside a R-LT node
1107 :param namedE: a named-E node
1108 :param table: dictionnary returned by the decode function
1109 :param varList: None or a VarList object in which varaibles are searched for
1110 """
1111 # Before A(:): <f:named-E>
1112 # <f:N><f:n>A</f:n></f:N>
1113 # <f:R-LT>
1114 # <f:array-R>(
1115 # <f:section-subscript-LT>
1116 # <f:section-subscript>:</f:section-subscript>
1117 # </f:section-subscript-LT>)
1118 # </f:array-R>
1119 # </f:R-LT>
1120 # </f:named-E>
1121 # After A(I): <f:named-E>
1122 # <f:N><f:n>A</f:n></f:N>
1123 # <f:R-LT>
1124 # <f:parens-R>(
1125 # <f:element-LT>
1126 # <f:element><f:named-E><f:N><f:n>I</f:n></f:N></f:named-E></f:element>
1127 # </f:element-LT>)
1128 # </f:parens-R>
1129 # </f:R-LT>
1130 # </f:named-E>
1131
1132 nodeRLT = namedE.find('./{*}R-LT')
1133 arrayR = nodeRLT.find('./{*}array-R') # Not always in first position, eg: ICED%XRTMIN(:)
1134 if arrayR is not None:
1135 index = list(nodeRLT).index(arrayR)
1136 parensR = createElem('parens-R', text='(', tail=')')
1137 elementLT = createElem('element-LT')
1138 parensR.append(elementLT)
1139 ivar = -1
1140 for ss in nodeRLT[index].findall('./{*}section-subscript-LT/{*}section-subscript'):
1141 element = createElem('element', tail=', ')
1142 elementLT.append(element)
1143 if ':' in alltext(ss):
1144 ivar += 1
1145 varName = list(table.keys())[ivar] # variable name
1146 lower = ss.find('./{*}lower-bound')
1147 upper = ss.find('./{*}upper-bound')
1148 if lower is not None:
1149 lower = alltext(lower)
1150 if upper is not None:
1151 upper = alltext(upper)
1152 if lower is not None and ss.text is not None and ':' in ss.text:
1153 # fxtran bug workaround
1154 upper = lower
1155 lower = None
1156 if lower is None and upper is None:
1157 # E.g. 'A(:)'
1158 # In this case we use the DO loop bounds without checking validity with
1159 # respect to the array declared bounds
1160 element.append(createExprPart(varName))
1161 else:
1162 # E.g.:
1163 # !$mnh_expand_array(JI=2:15)
1164 # A(2:15) or A(:15) or A(2:)
1165 if lower is None:
1166 # lower bound not defined, getting lower declared bound for this array
1167 lower = self.varList.findVar(n2name(namedE.find('{*}N')),
1168 array=True)['as'][ivar][0]
1169 if lower is None:
1170 lower = '1' # default fortran lower bound
1171 elif upper is None:
1172 # upper bound not defined, getting upper declared bound for this array
1173 upper = self.varList.findVar(n2name(namedE.find('{*}N')),
1174 array=True)['as'][ivar][1]
1175 # If the DO loop starts from JI=I1 and goes to JI=I2; and array
1176 # bounds are J1:J2
1177 # We compute J1-I1+JI and J2-I2+JI and they should be the same
1178 # E.g: array bounds could be 'I1:I2' (becoming JI:JI) or 'I1+1:I2+1"
1179 # (becoming JI+1:JI+1)
1180 newlower = simplifyExpr(lower, add=varName, sub=table[varName][0])
1181 newupper = simplifyExpr(upper, add=varName, sub=table[varName][1])
1182 if newlower != newupper:
1183 raise PYFTError(("Don't know how to do with an array declared with " +
1184 "'{la}:{ua}' and a loop from '{ll}' to '{ul}'"
1185 ).format(la=lower, ua=upper,
1186 ll=table[varName][0],
1187 ul=table[varName][1]))
1188 element.append(createExprPart(newlower))
1189 else:
1190 element.append(ss.find('./{*}lower-bound'))
1191 element.tail = None # last element
1192 nodeRLT.remove(nodeRLT[index])
1193 nodeRLT.insert(index, parensR)
1194
1195 @debugDecor
1196 def findArrayBounds(self, arr, loopVar, extraVarList=None):
1197 """
1198 Find bounds and loop variable given an array
1199 :param arr: array node (named-E node with a array-R child)
1200 :param loopVar: None to create new variable for each added DO loop
1201 or a function that return the name of the variable to use for the loop
1202 control.
1203 This function returns a string (name of the variable), or True to create
1204 a new variable, or False to not transform this statement
1205 The functions takes as arguments:
1206 - lower and upper bounds as defined in the declaration statement
1207 - lower and upper bounds as given in the statement
1208 - name of the array
1209 - index of the rank
1210 :param extraVarList: None or list of variables (such as those contained in a VarList object)
1211 defined but not yet available in the self.varList object.
1212 :return: the tuple (table, newVar) where:
1213 table is a dictionnary: keys are loop variable names
1214 values are tuples with lower and upper bounds
1215 newVar is a list of loop variables not found in varList. This list has the same
1216 format as the varList list.
1217
1218 In case the loop variable cannot be defined, the function returns (None, [])
1219 """
1220 table = {} # ordered since python 3.7
1221 name = n2name(arr.find('./{*}N'))
1222 varNew = []
1223 extraVarList = extraVarList if extraVarList is not None else []
1224
1225 # Iteration on the different subscript
1226 for iss, ss in enumerate(arr.findall('./{*}R-LT/{*}array-R/{*}section-subscript-LT/' +
1227 '{*}section-subscript')):
1228 # We are only interested by the subscript containing ':'
1229 # we must not iterate over the others, eg: X(:,1)
1230 if ':' in alltext(ss):
1231 # Look for loop variable name and lower/upper bounds for iteration
1232 varName, lower, upper = self.findIndexArrayBounds(arr, iss, loopVar)
1233 # varName can be a string (name to use), True (to create a variable),
1234 # False (to discard the array
1235 if varName is not False and varName in table:
1236 raise PYFTError(("The variable {var} must be used for the rank #{i1} whereas " +
1237 "it is already used for rank #{i2} (for array {name})."
1238 ).format(var=varName, i1=str(iss),
1239 i2=str(list(table.keys()).index(varName)),
1240 name=name))
1241 if varName is True:
1242 # A new variable must be created
1243 # We look for a variable name that don't already exist
1244 # We can reuse a newly created varaible only if it is not used for the previous
1245 # indexes of the same statement
1246 j = 0
1247 found = False
1248 while not found:
1249 j += 1
1250 varName = 'J' + str(j)
1251 var = self.varList.findVar(varName, extraVarList=extraVarList + varNew)
1252 if (var is None or var.get('new', False)) and varName not in table:
1253 found = True
1254 varDesc = {'as': [], 'asx': [], 'n': varName, 'i': None,
1255 't': 'INTEGER', 'arg': False, 'use': False, 'opt': False,
1256 'scopePath': self.path}
1257 if varDesc not in varNew:
1258 varNew.append(varDesc)
1259
1260 elif (varName is not False and
1261 self.varList.findVar(varName, array=False, exactScope=True) is None):
1262 # We must declare the variable
1263 varDesc = {'as': [], 'asx': [], 'n': varName, 'i': None,
1264 't': 'INTEGER', 'arg': False, 'use': False, 'opt': False,
1265 'scopePath': self.path}
1266 varNew.append(varDesc)
1267
1268 # fill table
1269 table[varName] = (lower, upper)
1270
1271 return (None, []) if False in table else (table, varNew)
1272
1273 @debugDecor
1274 @updateVarList
1275 def renameVar(self, oldName, newName):
1276 """
1277 :param oldName: old name of the variable
1278 :param newName: new name of the variable
1279 """
1280 for node in self.findall('.//{*}N'):
1281 if n2name(node).upper() == oldName.upper():
1282 # Remove all n tag but one
1283 for nnn in node.findall('./{*}n')[1:]:
1284 node.remove(nnn)
1285 # Fill the first n with the new name
1286 node.find('./{*}n').text = newName
1287
1288 @debugDecor
1289 def removeVarIfUnused(self, varList, excludeDummy=False, excludeModule=False, simplify=False):
1290 """
1291 :param varList: list of variables to remove if unused. Each item is a list or tuple of two
1292 elements.
1293 The first one describes where the variable is used, the second one is
1294 the name of the variable. The first element is a '/'-separated path with
1295 each element having the form 'module:<name of the module>',
1296 'sub:<name of the subroutine>' or 'func:<name of the function>'
1297 :param excludeDummy: if True, dummy arguments are always kept untouched
1298 :param excludeModule: if True, module variables are always kept untouched
1299 :param simplify: try to simplify code (if we delete a declaration statement that used a
1300 variable as kind selector, and if this variable is not used else where,
1301 we also delete it)
1302 :return: the varList without the unremovable variables
1303 If possible, remove the variable from declaration, and from the argument list if needed
1304 """
1305 varList = self._normalizeUniqVar(varList)
1306 if excludeModule:
1307 varList = [v for v in varList if v[0].split('/')[-1].split(':')[0] != 'module']
1308
1309 varUsed = self.isVarUsed(varList, dummyAreAlwaysUsed=excludeDummy)
1310 varListToRemove = []
1311 for scopePath, varName in varList:
1312 assert scopePath.split('/')[-1].split(':')[0] != 'type', \
1313 "The removeVarIfUnused cannot be used with type members"
1314 if not varUsed[(scopePath, varName)]:
1315 varListToRemove.append([scopePath, varName])
1316 self.removeVar(varListToRemove, simplify=simplify)
1317 return varListToRemove
1318
1319 @debugDecor
1320 def isVarUsed(self, varList, exactScope=False, dummyAreAlwaysUsed=False):
1321 """
1322 :param varList: list of variables to test. Each item is a list or tuple of two elements.
1323 The first one describes where the variable is declared, the second one is
1324 the name of the variable. The first element is a '/'-separated path with
1325 each element having the form 'module:<name of the module>',
1326 'sub:<name of the subroutine>' or 'func:<name of the function>'
1327 :param exactScope: True to search strictly in scope
1328 :param dummyAreAlwaysUsed: Returns True if variable is a dummy argument
1329 :return: a dict whose keys are the elements of varList, and values are True when the
1330 variable is used, False otherwise
1331
1332 If exactScope is True, the function will search for variable usage
1333 only in this scope. But this feature has a limited interest.
1334
1335 If exactScope is False:
1336 - if scopePath is a subroutine/function in a contains section,
1337 and if the variable is not declared in this scope, usages are
1338 searched in the module/subroutine/function upper that declared
1339 the variable and in all subroutines/functions in the contains section
1340 - if scopePath is a module/subroutine/function that has a
1341 contains sections, usages are searched in all subroutines/functions
1342 in the contains section
1343
1344 To know if a variable can be removed, you must use exactScope=False
1345 """
1346 varList = self._normalizeUniqVar(varList)
1347 # We must not limit to self.getScopes because var can be used upper than self
1348 allScopes = {scope.path: scope for scope in self.mainScope.getScopes()}
1349
1350 # Computes in which scopes variable must be searched
1351 if exactScope:
1352 locsVar = {(scopePath, varName): [scopePath]
1353 for scopePath, varName in varList}
1354 else:
1355 locsVar = {}
1356 for scopePath, varName in varList:
1357 # We search everywhere if var declaration is not found
1358 # Otherwise, we search from the scope where the variable is declared
1359 var = allScopes[scopePath].varList.findVar(varName)
1360 path = scopePath.split('/')[0] if var is None else var['scopePath']
1361
1362 # We start search from here but we must include all routines in contains
1363 # that do not declare again the same variable name
1364 testScopes = [path] # we must search in the current scope
1365 for scPath, sc in allScopes.items():
1366 if scPath.startswith(path + '/') and \
1367 scPath.split('/')[-1].split(':')[0] != 'type':
1368 # sc is a scope path contained inside path and is not a type declaration
1369 if sc.varList.findVar(varName, exactScope=True) is None:
1370 # There is not another variable with same name declared inside
1371 testScopes.append(scPath) # if variable is used here, it is used
1372 locsVar[(scopePath, varName)] = testScopes
1373
1374 # For each scope to search, list all the variables used
1375 usedVar = {}
1376 for scopePath in list(set(item for sublist in locsVar.values() for item in sublist)):
1377 usedVar[scopePath] = []
1378 # Loop on all child in the scope
1379 for node in allScopes[scopePath]:
1380 # we don't want use statement, it could be where the variable is declared,
1381 # not a usage place
1382 if not tag(node) == 'use-stmt':
1383 if tag(node) == 'T-decl-stmt':
1384 # We don't want the part with the list of declared variables, we only want
1385 # to capture variables used in the kind selector or in the shape
1386 # specification
1387 nodesN = node.findall('.//{*}_T-spec_//{*}N') + \
1388 node.findall('.//{*}shape-spec//{*}N')
1389 else:
1390 nodesN = node.findall('.//{*}N')
1391
1392 # We look for the variable name in these 'N' nodes.
1393 for nodeN in nodesN:
1394 if dummyAreAlwaysUsed:
1395 # No need to if the variable is a dummy argument; because if it is
1396 # one it will be found in the argument list of the subroutine/function
1397 # and will be considered as used
1398 usedVar[scopePath].append(n2name(nodeN).upper())
1399 else:
1400 parPar = allScopes[scopePath].getParent(nodeN, 2) # parent of parent
1401 # We exclude dummy argument list to really check if the variable is used
1402 # and do not only appear as an argument of the subroutine/function
1403 if parPar is None or not tag(parPar) == 'dummy-arg-LT':
1404 usedVar[scopePath].append(n2name(nodeN).upper())
1405
1406 result = {}
1407 for scopePath, varName in varList:
1408 assert scopePath.split('/')[-1].split(':')[0] != 'type', \
1409 'We cannot check type component usage'
1410 result[(scopePath, varName)] = any(varName.upper() in usedVar[scopePath]
1411 for scopePath in locsVar[(scopePath, varName)])
1412
1413 return result
1414
1415 @debugDecor
1416 @updateVarList
1417 def addArgInTree(self, varName, declStmt, pos, stopScopes, moduleVarList=None,
1418 otherNames=None,
1419 parserOptions=None, wrapH=False):
1420 """
1421 Adds an argument to the routine and propagates it upward until we encounter a scope
1422 where the variable exists or a scope in stopScopes
1423 :param varName: variable name
1424 :param declStmt: declarative statment (will be used by addVar)
1425 :param pos: position of the variable in the list of dummy argument
1426 :param stopScopes: list of scopes to reach
1427 :param moduleVarList: list of module variable specification to insert in the xml code
1428 a module variable specification is a list of two elements:
1429 - module name
1430 - variable name or or list of variable names
1431 or None to add a USE statement without the ONLY attribute
1432 use moduleVarList to not add module variables
1433 :param otherNames: None or list of other variable names that can be used
1434 These variables are used first
1435 :param parserOptions, wrapH: see the PYFT class
1436
1437 Argument is inserted only on paths leading to one of scopes listed in stopScopes
1438 """
1439 def insertInArgList(varName, varNameToUse, pos, callFuncStmt):
1440 """
1441 Insert varName in the list of arguments to the subroutine or function call
1442 :param varName: name of the dummy argument
1443 :param varNameToUse: name of the variable
1444 :param pos: inclusion position
1445 :param callFuncStmt: call statement or function call
1446 """
1447 argList = callFuncStmt.find('./{*}R-LT/{*}parens-R/{*}element-LT')
1448 if argList is not None:
1449 container = createElem('element')
1450 else:
1451 argList = callFuncStmt.find('./{*}arg-spec')
1452 container = createElem('arg')
1453 if argList is None:
1454 # Call without argument
1455 callFuncStmt.find('./{*}procedure-designator').tail = '('
1456 argList = createElem('arg-spec', tail=')')
1457 callFuncStmt.append(argList)
1458 item = createExprPart(varNameToUse)
1459 previous = pos - 1 if pos >= 0 else len(argList) + pos # convert negative pos
1460 while previous >= 0 and tag(argList[previous]) in ('C', 'cnt'):
1461 previous -= 1
1462 following = pos if pos > 0 else len(argList) + pos + 1 # convert negative pos
1463 while following <= len(argList) - 1 and tag(argList[following]) in ('C', 'cnt'):
1464 following += 1
1465 if (previous >= 0 and argList[previous].find('./{*}arg-N/{*}k') is not None) or \
1466 (following <= len(argList) - 1 and
1467 argList[following].find('./{*}arg-N/{*}k') is not None) or \
1468 following == len(argList):
1469 # We use the key=val syntax whereever it is possible because it's safer in case of
1470 # optional arguments
1471 # If previous arg, or following arg is already with a key=val syntax, we can (must)
1472 # use it
1473 # If the inserted argument is the last one of the list, it also can use this syntax
1474 k = createElem('k', text=varName)
1475 argN = createElem('arg-N', tail='=')
1476 argN.append(k)
1477 argN.set('n', varName)
1478 container.append(argN)
1479 container.append(item)
1480 self.insertInList(pos, container, argList)
1481
1482 if self.path in stopScopes or self.tree.isUnderStopScopes(self.path, stopScopes):
1483 # We are on the path to a scope in the stopScopes list, or scopeUp is one of the
1484 # stopScopes
1485 var = self.varList.findVar(varName, exactScope=True)
1486 if otherNames is not None:
1487 vOther = [self.varList.findVar(v, exactScope=True) for v in otherNames]
1488 vOther = [v for v in vOther if v is not None]
1489 if len(vOther) > 0:
1490 var = vOther[-1]
1491
1492 if var is None:
1493 # The variable doesn't exist in this scope, we add it
1494 self.addVar([[self.path, varName, declStmt, pos]])
1495 if moduleVarList is not None:
1496 # Module variables must be added when var is added
1497 self.addModuleVar([(self.path, moduleName, moduleVarNames)
1498 for (moduleName, moduleVarNames) in moduleVarList])
1499
1500 # We look for interface declaration if subroutine is directly accessible
1501 if len(self.path.split('/')) == 1:
1502 filename, scopePathInterface = self.tree.findScopeInterface(self.path)
1503 if filename is not None:
1504 pft = None
1505 try:
1506 if self.getFileName() == os.path.normpath(filename):
1507 # interface declared in same file
1508 xml = self.mainScope
1509 pft = None
1510 else:
1512 filename, parserOptions, wrapH, tree=self.tree,
1513 clsPYFT=self._mainScope.__class__)
1514 xml = pft
1515 scopeInterface = xml.getScopeNode(scopePathInterface)
1516 varInterface = scopeInterface.varList.findVar(varName, exactScope=True)
1517 if varInterface is None:
1518 scopeInterface.addVar([[scopePathInterface, varName,
1519 declStmt, pos]])
1520 if moduleVarList is not None:
1521 # Module variables must be added when var is added
1522 xml.addModuleVar(
1523 [(scopePathInterface, moduleName, moduleVarNames)
1524 for (moduleName, moduleVarNames) in moduleVarList])
1525 if pft is not None:
1526 pft.write()
1527 finally:
1528 if pft is not None:
1529 pft.close()
1530
1531 if var is None and self.path not in stopScopes:
1532 # We must propagates upward
1533 # scopes calling the current scope
1534 for scopePathUp in self.tree.calledByScope(self.path):
1535 if scopePathUp in stopScopes or self.tree.isUnderStopScopes(scopePathUp,
1536 stopScopes):
1537 # We are on the path to a scope in the stopScopes list, or scopePathUp is
1538 # one of the stopScopes
1539 # can be defined several times?
1540 for filename in self.tree.scopeToFiles(scopePathUp):
1541 pft = None
1542 try:
1543 if self.getFileName() == os.path.normpath(filename):
1544 # Upper scope is in the same file
1545 xml = self.mainScope
1546 pft = None
1547 else:
1549 filename, parserOptions, wrapH,
1550 tree=self.tree,
1551 clsPYFT=self._mainScope.__class__)
1552 xml = pft
1553 scopeUp = xml.getScopeNode(scopePathUp)
1554 # Add the argument and propagate upward
1555 scopeUp.addArgInTree(
1556 varName, declStmt, pos,
1557 stopScopes, moduleVarList, otherNames,
1558 parserOptions=parserOptions,
1559 wrapH=wrapH)
1560 # Add the argument to calls (subroutine or function)
1561 name = self.path.split('/')[-1].split(':')[1].upper()
1562 isCalled = False
1563 varNameToUse = varName
1564 if otherNames is not None:
1565 vOther = [scopeUp.varList.findVar(v, exactScope=True)
1566 for v in otherNames]
1567 vOther = [v for v in vOther if v is not None]
1568 if len(vOther) > 0:
1569 varNameToUse = vOther[-1]['n']
1570 if self.path.split('/')[-1].split(':')[0] == 'sub':
1571 # We look for call statements
1572 for callStmt in scopeUp.findall('.//{*}call-stmt'):
1573 callName = n2name(callStmt.find(
1574 './{*}procedure-designator/{*}named-E/{*}N')).upper()
1575 if callName == name:
1576 insertInArgList(varName, varNameToUse, pos, callStmt)
1577 isCalled = True
1578 else:
1579 # We look for function use
1580 for funcCall in scopeUp.findall(
1581 './/{*}named-E/{*}R-LT/{*}parens-R/' +
1582 '{*}element-LT/../../..'):
1583 funcName = n2name(funcCall.find('./{*}N')).upper()
1584 if funcName == name:
1585 insertInArgList(varName, varNameToUse, pos, funcCall)
1586 isCalled = True
1587 if pft is not None:
1588 pft.write()
1589 finally:
1590 if pft is not None:
1591 pft.close()
1592
1593 if isCalled:
1594 # We must check in the scope (or upper scopes) if an interface
1595 # block declares the routine
1596 for interface in self.findall('.//{*}interface-construct/{*}' +
1597 'program-unit/{*}subroutine-stmt/' +
1598 '{*}subroutine-N/{*}N/../../../'):
1599 if n2name(interface.find('./{*}subroutine-stmt/' +
1600 '{*}subroutine-N/' +
1601 '{*}N')).upper() == name:
1602 # We must add the argument to the interface
1603 raise PYFTError('This case is not yet implemented')
restrict(self, scopePath, excludeContains)
Definition variables.py:171
findVar(self, varName, array=None, exactScope=False, extraVarList=None)
Definition variables.py:185
__init__(self, mainScope, _preCompute=None)
Definition variables.py:48
renameVar(self, oldName, newName)
removeUnusedLocalVar(self, excludeList=None, simplify=False)
Definition variables.py:705
isVarUsed(self, varList, exactScope=False, dummyAreAlwaysUsed=False)
findArrayBounds(self, arr, loopVar, extraVarList=None)
_normalizeScopeVar(self, scopeVarList)
Definition variables.py:274
addArgInTree(self, varName, declStmt, pos, stopScopes, moduleVarList=None, otherNames=None, parserOptions=None, wrapH=False)
arrayR2parensR(self, namedE, table)
addModuleVar(self, moduleVarList)
Definition variables.py:594
checkUnusedLocalVar(self, mustRaise=False, excludeList=None)
Definition variables.py:673
addArrayParenthesesInNode(self, node)
Definition variables.py:825
removeArrayParenthesesInNode(self, node)
Definition variables.py:857
checkIntent(self, mustRaise=False)
Definition variables.py:349
modifyAutomaticArrays(self, declTemplate=None, startTemplate=None, endTemplate=None)
Definition variables.py:885
addExplicitArrayBounds(self, node=None)
Definition variables.py:725
_normalizeUniqVar(self, scopeVarList)
Definition variables.py:283
removeVarIfUnused(self, varList, excludeDummy=False, excludeModule=False, simplify=False)
checkImplicitNone(self, mustRaise=False)
Definition variables.py:327
findIndexArrayBounds(self, arr, index, loopVar)
removeVar(self, varList, simplify=False)
Definition variables.py:370
conservativePYFT(filename, parserOptions, wrapH, tree=None, verbosity=None, clsPYFT=None)
Definition pyfortool.py:20
_getDeclStmtTag(scopePath)
Definition variables.py:19