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 unued 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 removeUnusedLocalVar(self, excludeList=None, simplify=False):
674 """
675 Remove unused local variables (dummy and module variables are not suppressed)
676 :param excludeList: list of variable names to exclude from removal (even if unused)
677 :param simplify: try to simplify code (if we delete a declaration statement that used a
678 variable as kind selector, and if this variable is not used else where,
679 we also delete it)
680 """
681 if excludeList is None:
682 excludeList = []
683 else:
684 excludeList = [item.upper() for item in excludeList]
685
686 allVar = [(scope.path, v['n'])
687 for scope in self.getScopes(excludeKinds=['type'])
688 for v in scope.varList
689 if v['n'].upper() not in excludeList and v['scopePath'] == scope.path]
690 self.removeVarIfUnused(allVar, excludeDummy=True, excludeModule=True, simplify=simplify)
691
692 @debugDecor
693 def addExplicitArrayBounds(self, node=None):
694 """
695 Replace ':' by explicit arrays bounds.
696 :param node: xml node in which ':' must be replaced (None to replace everywhere)
697 """
698 if node is None:
699 nodes = [(scope, scope) for scope in self.getScopes()]
700 else:
701 nodes = [(self, node)]
702
703 for (scope, childNode) in nodes:
704 for parent4 in [parent4 for parent4
705 in childNode.findall('.//{*}section-subscript/../../../..')
706 if parent4.find('./{*}R-LT/{*}component-R') is None]:
707 # Shape of type members is unknown
708 for parent in parent4.findall('.//{*}section-subscript/..'):
709 for sub in parent.findall('.//{*}section-subscript'):
710 lowerUsed = sub.find('./{*}lower-bound')
711 upperUsed = sub.find('./{*}upper-bound')
712 # A slice can be A(:), A(I:) or A(:I), but not A(I)
713 # With A(:) and A(:I), lowerUsed is None
714 # With A(I:) lowerUsed.tail contains a ':'
715 # With A(I) lowerUsed.tail doesn't contain a ':'
716 if lowerUsed is None or \
717 (lowerUsed.tail is not None and ':' in lowerUsed.tail):
718 if lowerUsed is None or upperUsed is None:
719 # At least one array bound is implicit
720 varDesc = scope.varList.findVar(n2name(parent4.find('.//{*}N')))
721 if varDesc is not None and varDesc['t'] is not None and \
722 'CHAR' not in varDesc['t']: # module array or character
723 lowerDecl, upperDecl = varDesc['as'][list(parent).index(sub)]
724 if lowerDecl is None:
725 lowerDecl = '1'
726 # When a bound is explicit, we keep it, otherwise we
727 # take the declared bound
728 lowerBound = lowerDecl if lowerUsed is None \
729 else alltext(lowerUsed)
730 upperBound = upperDecl if upperUsed is None \
731 else alltext(upperUsed)
732 if upperBound is not None: # case of implicit shape
733 lowerXml, upperXml = createArrayBounds(lowerBound,
734 upperBound,
735 'ARRAY')
736 # We remove current explicit bounds or the ':',
737 # and replace them by the new explicit declaration
738 for nnn in sub:
739 if tag(nnn) in ('lower-bound', 'upper-bound'):
740 sub.remove(nnn)
741 else:
742 raise PYFTError("Unexpected case, " +
743 "tag is {}".format(tag(nnn)))
744 sub.text = '' # Delete the initial ':'
745 sub.extend([lowerXml, upperXml])
746
747 @debugDecor
748 @noParallel
750 """
751 Look for arrays and add parenthesis. A => A(:)
752 """
753 # Loop on scopes
754 for scope in self.getScopes():
755 for node in scope.iter():
756 # Arrays are used in statements
757 # * We must exclude allocate-stmt, deallocate-stmt, pointer-a-stmt, T-decl-stmt and
758 # associate-stmt,
759 # nullify-stmt function-stmt, subroutine-stmt, interface-stmt must be kept
760 # untouched
761 # action-stmt is discarded as it contains another statement.
762 # * Array variables to modify can be found in
763 # - simple affectation (a-stmt),
764 # - if construct conditions (if-then-stmt, else-if-stmt),
765 # - where-construct mask (where-construct-stmt, else-where-stmt),
766 # - if statement condition (if-stmt/condition-E, be carefull to not take the
767 # whole if-stmt as it contains the action-stmt which,
768 # in turn, can contain an allocate/deallocate/pointer
769 # assignment)
770 # - where statement mask (where-stmt/mask-E and not directly
771 # where-stmt as for if-stmt),
772 # - select-case-stmt and case-stmt,
773 # - do-stmt or forall-construct-stmt (e.g. FOR I=LBOUND(A, 0), UBOUND(A, 0))
774 # - forall-stmt/forall-triplet-spec-LT
775 nodeToTransform = None
776 if tag(node) in ('allocate-stmt', 'deallocate-stmt', 'pointer-a-stmt',
777 'T-decl-stmt', 'associate-stmt', 'function-stmt',
778 'subroutine-stmt', 'interface-stmt', 'action-stmt',
779 'nullify-stmt'):
780 # excluded
781 pass
782 elif tag(node) in ('if-stmt', 'where-stmt', 'forall-stmt'):
783 # We must transform only a part of the node
784 part = {'if-stmt': 'condition-E', 'where-stmt': 'mask-E',
785 'forall-stmt': 'forall-triplet-spec-LT'}[tag(node)]
786 nodeToTransform = node.find('./{*}' + part)
787 elif tag(node).endswith('-stmt'):
788 nodeToTransform = node
789 if nodeToTransform is not None:
790 scope.addArrayParenthesesInNode(nodeToTransform)
791
792 @debugDecor
794 """
795 Look for arrays and add parenthesis. A => A(:)
796 :param node: xml node in which ':' must be added
797 """
798 # Loop on variables
799 for namedE in node.findall('.//{*}named-E'):
800 if not namedE.find('./{*}R-LT'): # no parentheses
801 if not self.isNodeInProcedure(namedE, ('ALLOCATED', 'ASSOCIATED', 'PRESENT')):
802 # Pointer/allocatable used in ALLOCATED/ASSOCIATED must not be modified
803 # Array in present must not be modified
804 nodeN = namedE.find('./{*}N')
805 var = self.varList.findVar(n2name(nodeN))
806 if var is not None and var['as'] is not None and len(var['as']) > 0 and \
807 not ((var['pointer'] or var['allocatable']) and self.isNodeInCall(namedE)):
808 # This is a known array variable, with no parentheses
809 # But we exclude pointer allocatable in call statement because the called
810 # subroutine can wait for a pointer/allocatable and not an array (and it
811 # is difficult to guess as it would need to find the source code of the
812 # subroutine)
813 nodeRLT = createElem('R-LT')
814 namedE.insert(list(namedE).index(nodeN) + 1, nodeRLT)
815 arrayR = createElem('array-R', text='(')
816 nodeRLT.append(arrayR)
817 sectionSubscriptLT = createElem('section-subscript-LT', tail=')')
818 arrayR.append(sectionSubscriptLT)
819 for _ in var['as']:
820 sectionSubscript = createElem('section-subscript', text=':', tail=', ')
821 sectionSubscriptLT.append(sectionSubscript)
822 sectionSubscript.tail = None # last one
823
824 @debugDecor
825 @updateVarList
826 def modifyAutomaticArrays(self, declTemplate=None, startTemplate=None, endTemplate=None):
827 """
828 :param declTemplate: declaration template
829 :param startTemplate: template for the first executable statement
830 :param: endTemplate: template for the last executable statement
831 :return: number of arrays modified
832 Modifies all automatic arrays declaration in subroutine and functions. The declaration is
833 replaced by the declaration template, the start template is inserted as first executable
834 statement and the end template as last executable statement. Each template can use the
835 following place holders:
836 "{doubledotshape}", "{shape}", "{lowUpList}", "{name}" and "{type}" wich are, respectively
837 modified into ":, :, :", "I, I:J, 0:I", "1, I, I, J, 0, I", "A", "REAL" if the original
838 declaration statement was "A(I, I:J, 0:I)". The template
839 "{type}, DIMENSION({doubledotshape}), ALLOCATABLE :: {name}#
840 ALLOCATE({name}({shape}))#DEALLOCATE({name})"
841 replaces automatic arrays by allocatables
842 """
843 templates = {'decl': declTemplate if declTemplate is not None else '',
844 'start': startTemplate if startTemplate is not None else '',
845 'end': endTemplate if endTemplate is not None else ''} # ordered dict
846
847 number = 0
848 for scope in [scope for scope in self.getScopes()
849 if scope.path.split('/')[-1].split(':')[0] in ('sub', 'func')]:
850 # For all subroutine and function scopes
851 # Determine the list of variables to transform
852 varListToTransform = []
853 for var in [var for var in scope.varList
854 if var['as'] is not None and
855 len(var['as']) > 0 and
856 not (var['arg'] or var['allocatable'] or
857 var['pointer'] or var['result'])]:
858 # For all automatic arrays, which are not argument, not allocatable,
859 # not pointer and not result
860 if var['init'] is not None:
861 logging.warning("An array (%s) has an initial value, it can't be " +
862 "processed by modifyAutomaticArrays.)", var['n'])
863 else:
864 varListToTransform.append(var)
865 # A variable can make use of the size of another variable in its declaration statement
866 # We order the variables to not insert the declaration of a variable before the
867 # declaration of the variables it depends on
868 orderedVarListToTransform = []
869 while len(varListToTransform) > 0:
870 nAdded = 0
871 for var in varListToTransform[:]:
872 # flatten var['asx'] excluding None
873 listN = [x for dim in var['asx'] for x in dim if x is not None]
874 listN = [n2name(nodeN).upper() for asx in listN
875 for nodeN in asx.findall('.//{*}N/{*}n/..')]
876 if len(set(listN).intersection([v['n'].upper()
877 for v in varListToTransform])) == 0:
878 # Variable var does not use variables still in varListToTransform
879 varListToTransform.remove(var)
880 orderedVarListToTransform.append(var)
881 nAdded += 1
882 if nAdded == 0:
883 raise PYFTError('It seems that there is a circular reference in ' +
884 'the declaration statements')
885 # Loop on variable to transform
886 for var in orderedVarListToTransform[::-1]: # reverse order
887 number += 1
888 # Apply the template
889 templ = copy.deepcopy(templates)
890 for templPart in templ:
891 if '{doubledotshape}' in templ[templPart]:
892 templ[templPart] = templ[templPart].replace(
893 '{doubledotshape}', ','.join([':'] * len(var['as'])))
894 if '{shape}' in templ[templPart]:
895 result = []
896 for i in range(len(var['as'])):
897 if var['as'][i][0] is None:
898 result.append(var['as'][i][1])
899 else:
900 result.append(var['as'][i][0] + ':' + var['as'][i][1])
901 templ[templPart] = templ[templPart].replace('{shape}', ', '.join(result))
902 if '{name}' in templ[templPart]:
903 templ[templPart] = templ[templPart].replace('{name}', var['n'])
904 if '{type}' in templ[templPart]:
905 templ[templPart] = templ[templPart].replace('{type}', var['t'])
906 if '{lowUpList}' in templ[templPart]:
907 result = []
908 for i in range(len(var['as'])):
909 if var['as'][i][0] is None:
910 result.extend([1, var['as'][i][1]])
911 else:
912 result.extend([var['as'][i][0], var['as'][i][1]])
913 templ[templPart] = templ[templPart].replace(
914 '{lowUpList}', ', '.join([str(r) for r in result]))
915
916 # Get the xml for the template
917 separator = "!ABCDEFGHIJKLMNOPQRSTUVWabcdefghijklmnopqrstuvwxyz0123456789"
918 part = 0
919 for node in createExpr(templ['decl'] + '\n' + separator + '\n' +
920 templ['start'] + '\n' + separator + '\n' +
921 templ['end']):
922 templPart = list(templ.keys())[part]
923 if not isinstance(templ[templPart], list):
924 templ[templPart] = []
925 if tag(node) == 'C' and node.text == separator:
926 part += 1
927 else:
928 templ[templPart].append(node)
929
930 # Replace declaration statement
931 # We look for the last position in the declaration list which do not use the
932 # variable we add
933 # This algorithm will stop when the original declaration statement is encoutered
934 for decl in scope.findall('./{*}T-decl-stmt'):
935 index = list(scope).index(decl)
936 if var['n'] in [n2name(nodeN) for nodeN in decl.findall('.//{*}N')]:
937 break
938 scope.removeVar([(scope.path, var['n'])], simplify=False)
939 for nnn in templ['decl'][::-1]:
940 scope.insert(index, nnn)
941
942 # Insert statements
943 for nnn in templ['start'][::-1]:
944 scope.insertStatement(nnn, True)
945 for nnn in templ['end'][::-1]:
946 scope.insertStatement(nnn, False)
947 return number
948
949 @staticmethod
950 @debugDecor
951 def varSpec2stmt(varSpec):
952 """
953 :param varSpec: a variable description, same form as the items return by self.varList
954 :return: the associated declarative statement
955 """
956 if varSpec['use'] is not False:
957 stmt = f"USE {varSpec['use']}, ONLY: {varSpec['n']}"
958 else:
959 stmt = varSpec['t']
960 if varSpec['as']:
961 stmt += ', DIMENSION('
962 dl = []
963 for el in varSpec['as']:
964 if el[0] is None and el[1] is None:
965 dl.append(':')
966 elif el[0] is None:
967 dl.append(el[1])
968 else:
969 dl.append(el[0] + ':' + el[1])
970 if (varSpec['allocatable'] and not all(d == ':' for d in dl)) or \
971 (any(d == ':' for d in dl) and not varSpec['allocatable']):
972 raise PYFTError('Missing dim are mandatory and allowed ' +
973 'only for allocatable arrays')
974 stmt += ', '.join(dl) + ')'
975 if varSpec['allocatable']:
976 stmt += ", ALLOCATABLE"
977 if varSpec['parameter']:
978 stmt += ", PARAMETER"
979 if varSpec['i'] is not None:
980 stmt += ", INTENT(" + varSpec['i'] + ")"
981 if varSpec['opt'] is True:
982 stmt += ", OPTIONAL"
983 stmt += " :: " + varSpec['n']
984 if varSpec['init'] is not None:
985 stmt += "=" + varSpec['init']
986 return stmt
987
988 @debugDecor
989 def findIndexArrayBounds(self, arr, index, loopVar):
990 """
991 Find bounds and loop variable for a given array index
992 :param arr: array node (named-E node with a array-R child)
993 :param index: index of the rank of the array
994 :param loopVar: None to create new variable for each added DO loop
995 or a function that return the name of the variable to use for the
996 loop control.
997 This function returns a string (name of the variable), or True to create
998 a new variable, or False to not transform this statement
999 The functions takes as arguments:
1000 - lower and upper bounds as defined in the declaration statement
1001 - lower and upper bounds as given in the statement
1002 - name of the array
1003 - index of the rank
1004 :return: the tuple (loopName, lowerBound, upperBound) where:
1005 loopName is the name of the variable to use for the loop
1006 lower and upper bounds are the bounds to use for the DO loop
1007 loopName can be:
1008 - a string
1009 - False to discard this index
1010 - True to create a new variable to loop with
1011 """
1012 name = n2name(arr.find('./{*}N'))
1013 ss = arr.findall('./{*}R-LT/{*}array-R/{*}section-subscript-LT/{*}section-subscript')[index]
1014 # We are only interested by the subscript containing ':'
1015 # we must not iterate over the others, eg: X(:,1)
1016 if ':' in alltext(ss):
1017 # Look for lower and upper bounds for iteration and declaration
1018 lowerUsed = ss.find('./{*}lower-bound')
1019 upperUsed = ss.find('./{*}upper-bound')
1020 varDesc = self.varList.findVar(name, array=True)
1021 if varDesc is not None:
1022 lowerDecl, upperDecl = varDesc['as'][index]
1023 if lowerDecl is None:
1024 lowerDecl = '1' # default lower index for FORTRAN arrays
1025 else:
1026 lowerDecl, upperDecl = None, None
1027
1028 # name of the loop variable
1029 if loopVar is None:
1030 # loopVar is not defined, we create a new variable for the loop only if lower
1031 # and upper bounds have been found (easy way to discard character strings)
1032 varName = lowerDecl is not None and upperDecl is not None
1033 else:
1034 varName = loopVar(lowerDecl, upperDecl,
1035 None if lowerUsed is None else alltext(lowerUsed),
1036 None if upperUsed is None else alltext(upperUsed), name, index)
1037 return (varName,
1038 lowerDecl if lowerUsed is None else alltext(lowerUsed),
1039 upperDecl if upperUsed is None else alltext(upperUsed))
1040 return None
1041
1042 @debugDecor
1043 def arrayR2parensR(self, namedE, table):
1044 """
1045 Transform a array-R into a parens-R node by replacing slices by variables
1046 In 'A(:)', the ':' is in a array-R node whereas in 'A(JL)', 'JL' is in a parens-R node.
1047 Both the array-R and the parens-R nodes are inside a R-LT node
1048 :param namedE: a named-E node
1049 :param table: dictionnary returned by the decode function
1050 :param varList: None or a VarList object in which varaibles are searched for
1051 """
1052 # Before A(:): <f:named-E>
1053 # <f:N><f:n>A</f:n></f:N>
1054 # <f:R-LT>
1055 # <f:array-R>(
1056 # <f:section-subscript-LT>
1057 # <f:section-subscript>:</f:section-subscript>
1058 # </f:section-subscript-LT>)
1059 # </f:array-R>
1060 # </f:R-LT>
1061 # </f:named-E>
1062 # After A(I): <f:named-E>
1063 # <f:N><f:n>A</f:n></f:N>
1064 # <f:R-LT>
1065 # <f:parens-R>(
1066 # <f:element-LT>
1067 # <f:element><f:named-E><f:N><f:n>I</f:n></f:N></f:named-E></f:element>
1068 # </f:element-LT>)
1069 # </f:parens-R>
1070 # </f:R-LT>
1071 # </f:named-E>
1072
1073 nodeRLT = namedE.find('./{*}R-LT')
1074 arrayR = nodeRLT.find('./{*}array-R') # Not always in first position, eg: ICED%XRTMIN(:)
1075 if arrayR is not None:
1076 index = list(nodeRLT).index(arrayR)
1077 parensR = createElem('parens-R', text='(', tail=')')
1078 elementLT = createElem('element-LT')
1079 parensR.append(elementLT)
1080 ivar = -1
1081 for ss in nodeRLT[index].findall('./{*}section-subscript-LT/{*}section-subscript'):
1082 element = createElem('element', tail=', ')
1083 elementLT.append(element)
1084 if ':' in alltext(ss):
1085 ivar += 1
1086 varName = list(table.keys())[ivar] # variable name
1087 lower = ss.find('./{*}lower-bound')
1088 upper = ss.find('./{*}upper-bound')
1089 if lower is not None:
1090 lower = alltext(lower)
1091 if upper is not None:
1092 upper = alltext(upper)
1093 if lower is not None and ss.text is not None and ':' in ss.text:
1094 # fxtran bug workaround
1095 upper = lower
1096 lower = None
1097 if lower is None and upper is None:
1098 # E.g. 'A(:)'
1099 # In this case we use the DO loop bounds without checking validity with
1100 # respect to the array declared bounds
1101 element.append(createExprPart(varName))
1102 else:
1103 # E.g.:
1104 # !$mnh_expand_array(JI=2:15)
1105 # A(2:15) or A(:15) or A(2:)
1106 if lower is None:
1107 # lower bound not defined, getting lower declared bound for this array
1108 lower = self.varList.findVar(n2name(namedE.find('{*}N')),
1109 array=True)['as'][ivar][0]
1110 if lower is None:
1111 lower = '1' # default fortran lower bound
1112 elif upper is None:
1113 # upper bound not defined, getting upper declared bound for this array
1114 upper = self.varList.findVar(n2name(namedE.find('{*}N')),
1115 array=True)['as'][ivar][1]
1116 # If the DO loop starts from JI=I1 and goes to JI=I2; and array
1117 # bounds are J1:J2
1118 # We compute J1-I1+JI and J2-I2+JI and they should be the same
1119 # E.g: array bounds could be 'I1:I2' (becoming JI:JI) or 'I1+1:I2+1"
1120 # (becoming JI+1:JI+1)
1121 newlower = simplifyExpr(lower, add=varName, sub=table[varName][0])
1122 newupper = simplifyExpr(upper, add=varName, sub=table[varName][1])
1123 if newlower != newupper:
1124 raise PYFTError(("Don't know how to do with an array declared with " +
1125 "'{la}:{ua}' and a loop from '{ll}' to '{ul}'"
1126 ).format(la=lower, ua=upper,
1127 ll=table[varName][0],
1128 ul=table[varName][1]))
1129 element.append(createExprPart(newlower))
1130 else:
1131 element.append(ss.find('./{*}lower-bound'))
1132 element.tail = None # last element
1133 nodeRLT.remove(nodeRLT[index])
1134 nodeRLT.insert(index, parensR)
1135
1136 @debugDecor
1137 def findArrayBounds(self, arr, loopVar, extraVarList=None):
1138 """
1139 Find bounds and loop variable given an array
1140 :param arr: array node (named-E node with a array-R child)
1141 :param loopVar: None to create new variable for each added DO loop
1142 or a function that return the name of the variable to use for the loop
1143 control.
1144 This function returns a string (name of the variable), or True to create
1145 a new variable, or False to not transform this statement
1146 The functions takes as arguments:
1147 - lower and upper bounds as defined in the declaration statement
1148 - lower and upper bounds as given in the statement
1149 - name of the array
1150 - index of the rank
1151 :param extraVarList: None or list of variables (such as those contained in a VarList object)
1152 defined but not yet available in the self.varList object.
1153 :return: the tuple (table, newVar) where:
1154 table is a dictionnary: keys are loop variable names
1155 values are tuples with lower and upper bounds
1156 newVar is a list of loop variables not found in varList. This list has the same
1157 format as the varList list.
1158
1159 In case the loop variable cannot be defined, the function returns (None, [])
1160 """
1161 table = {} # ordered since python 3.7
1162 name = n2name(arr.find('./{*}N'))
1163 varNew = []
1164 extraVarList = extraVarList if extraVarList is not None else []
1165
1166 # Iteration on the different subscript
1167 for iss, ss in enumerate(arr.findall('./{*}R-LT/{*}array-R/{*}section-subscript-LT/' +
1168 '{*}section-subscript')):
1169 # We are only interested by the subscript containing ':'
1170 # we must not iterate over the others, eg: X(:,1)
1171 if ':' in alltext(ss):
1172 # Look for loop variable name and lower/upper bounds for iteration
1173 varName, lower, upper = self.findIndexArrayBounds(arr, iss, loopVar)
1174 # varName can be a string (name to use), True (to create a variable),
1175 # False (to discard the array
1176 if varName is not False and varName in table:
1177 raise PYFTError(("The variable {var} must be used for the rank #{i1} whereas " +
1178 "it is already used for rank #{i2} (for array {name})."
1179 ).format(var=varName, i1=str(iss),
1180 i2=str(list(table.keys()).index(varName)),
1181 name=name))
1182 if varName is True:
1183 # A new variable must be created
1184 # We look for a variable name that don't already exist
1185 # We can reuse a newly created varaible only if it is not used for the previous
1186 # indexes of the same statement
1187 j = 0
1188 found = False
1189 while not found:
1190 j += 1
1191 varName = 'J' + str(j)
1192 var = self.varList.findVar(varName, extraVarList=extraVarList + varNew)
1193 if (var is None or var.get('new', False)) and varName not in table:
1194 found = True
1195 varDesc = {'as': [], 'asx': [], 'n': varName, 'i': None,
1196 't': 'INTEGER', 'arg': False, 'use': False, 'opt': False,
1197 'scopePath': self.path}
1198 if varDesc not in varNew:
1199 varNew.append(varDesc)
1200
1201 elif (varName is not False and
1202 self.varList.findVar(varName, array=False, exactScope=True) is None):
1203 # We must declare the variable
1204 varDesc = {'as': [], 'asx': [], 'n': varName, 'i': None,
1205 't': 'INTEGER', 'arg': False, 'use': False, 'opt': False,
1206 'scopePath': self.path}
1207 varNew.append(varDesc)
1208
1209 # fill table
1210 table[varName] = (lower, upper)
1211
1212 return (None, []) if False in table else (table, varNew)
1213
1214 @debugDecor
1215 @updateVarList
1216 def renameVar(self, oldName, newName):
1217 """
1218 :param oldName: old name of the variable
1219 :param newName: new name of the variable
1220 """
1221 for node in self.findall('.//{*}N'):
1222 if n2name(node).upper() == oldName.upper():
1223 # Remove all n tag but one
1224 for nnn in node.findall('./{*}n')[1:]:
1225 node.remove(nnn)
1226 # Fill the first n with the new name
1227 node.find('./{*}n').text = newName
1228
1229 @debugDecor
1230 def removeVarIfUnused(self, varList, excludeDummy=False, excludeModule=False, simplify=False):
1231 """
1232 :param varList: list of variables to remove if unused. Each item is a list or tuple of two
1233 elements.
1234 The first one describes where the variable is used, the second one is
1235 the name of the variable. The first element is a '/'-separated path with
1236 each element having the form 'module:<name of the module>',
1237 'sub:<name of the subroutine>' or 'func:<name of the function>'
1238 :param excludeDummy: if True, dummy arguments are always kept untouched
1239 :param excludeModule: if True, module variables are always kept untouched
1240 :param simplify: try to simplify code (if we delete a declaration statement that used a
1241 variable as kind selector, and if this variable is not used else where,
1242 we also delete it)
1243 :return: the varList without the unremovable variables
1244 If possible, remove the variable from declaration, and from the argument list if needed
1245 """
1246 varList = self._normalizeUniqVar(varList)
1247 if excludeModule:
1248 varList = [v for v in varList if v[0].split('/')[-1].split(':')[0] != 'module']
1249
1250 varUsed = self.isVarUsed(varList, dummyAreAlwaysUsed=excludeDummy)
1251 varListToRemove = []
1252 for scopePath, varName in varList:
1253 assert scopePath.split('/')[-1].split(':')[0] != 'type', \
1254 "The removeVarIfUnused cannot be used with type members"
1255 if not varUsed[(scopePath, varName)]:
1256 varListToRemove.append([scopePath, varName])
1257 self.removeVar(varListToRemove, simplify=simplify)
1258 return varListToRemove
1259
1260 @debugDecor
1261 def isVarUsed(self, varList, exactScope=False, dummyAreAlwaysUsed=False):
1262 """
1263 :param varList: list of variables to test. Each item is a list or tuple of two elements.
1264 The first one describes where the variable is declared, the second one is
1265 the name of the variable. The first element is a '/'-separated path with
1266 each element having the form 'module:<name of the module>',
1267 'sub:<name of the subroutine>' or 'func:<name of the function>'
1268 :param exactScope: True to search strictly in scope
1269 :param dummyAreAlwaysUsed: Returns True if variable is a dummy argument
1270 :return: a dict whose keys are the elements of varList, and values are True when the
1271 variable is used, False otherwise
1272
1273 If exactScope is True, the function will search for variable usage
1274 only in this scope. But this feature has a limited interest.
1275
1276 If exactScope is False:
1277 - if scopePath is a subroutine/function in a contains section,
1278 and if the variable is not declared in this scope, usages are
1279 searched in the module/subroutine/function upper that declared
1280 the variable and in all subroutines/functions in the contains section
1281 - if scopePath is a module/subroutine/function that has a
1282 contains sections, usages are searched in all subroutines/functions
1283 in the contains section
1284
1285 To know if a variable can be removed, you must use exactScope=False
1286 """
1287 varList = self._normalizeUniqVar(varList)
1288 # We must not limit to self.getScopes because var can be used upper than self
1289 allScopes = {scope.path: scope for scope in self.mainScope.getScopes()}
1290
1291 # Computes in which scopes variable must be searched
1292 if exactScope:
1293 locsVar = {(scopePath, varName): [scopePath]
1294 for scopePath, varName in varList}
1295 else:
1296 locsVar = {}
1297 for scopePath, varName in varList:
1298 # We search everywhere if var declaration is not found
1299 # Otherwise, we search from the scope where the variable is declared
1300 var = allScopes[scopePath].varList.findVar(varName)
1301 path = scopePath.split('/')[0] if var is None else var['scopePath']
1302
1303 # We start search from here but we must include all routines in contains
1304 # that do not declare again the same variable name
1305 testScopes = [path] # we must search in the current scope
1306 for scPath, sc in allScopes.items():
1307 if scPath.startswith(path + '/') and \
1308 scPath.split('/')[-1].split(':')[0] != 'type':
1309 # sc is a scope path contained inside path and is not a type declaration
1310 if sc.varList.findVar(varName, exactScope=True) is None:
1311 # There is not another variable with same name declared inside
1312 testScopes.append(scPath) # if variable is used here, it is used
1313 locsVar[(scopePath, varName)] = testScopes
1314
1315 # For each scope to search, list all the variables used
1316 usedVar = {}
1317 for scopePath in list(set(item for sublist in locsVar.values() for item in sublist)):
1318 usedVar[scopePath] = []
1319 # Loop on all child in the scope
1320 for node in allScopes[scopePath]:
1321 # we don't want use statement, it could be where the variable is declared,
1322 # not a usage place
1323 if not tag(node) == 'use-stmt':
1324 if tag(node) == 'T-decl-stmt':
1325 # We don't want the part with the list of declared variables, we only want
1326 # to capture variables used in the kind selector or in the shape
1327 # specification
1328 nodesN = node.findall('.//{*}_T-spec_//{*}N') + \
1329 node.findall('.//{*}shape-spec//{*}N')
1330 else:
1331 nodesN = node.findall('.//{*}N')
1332
1333 # We look for the variable name in these 'N' nodes.
1334 for nodeN in nodesN:
1335 if dummyAreAlwaysUsed:
1336 # No need to check if the variable is a dummy argument; because if it is
1337 # one it will be found in the argument list of the subroutine/function
1338 # and will be considered as used
1339 usedVar[scopePath].append(n2name(nodeN).upper())
1340 else:
1341 parPar = allScopes[scopePath].getParent(nodeN, 2) # parent of parent
1342 # We exclude dummy argument list to really check if the variable is used
1343 # and do not only appear as an argument of the subroutine/function
1344 if parPar is None or not tag(parPar) == 'dummy-arg-LT':
1345 usedVar[scopePath].append(n2name(nodeN).upper())
1346
1347 result = {}
1348 for scopePath, varName in varList:
1349 assert scopePath.split('/')[-1].split(':')[0] != 'type', \
1350 'We cannot check type component usage'
1351 result[(scopePath, varName)] = any(varName.upper() in usedVar[scopePath]
1352 for scopePath in locsVar[(scopePath, varName)])
1353
1354 return result
1355
1356 @debugDecor
1357 @updateVarList
1358 def addArgInTree(self, varName, declStmt, pos, stopScopes, moduleVarList=None,
1359 otherNames=None,
1360 parserOptions=None, wrapH=False):
1361 """
1362 Adds an argument to the routine and propagates it upward until we encounter a scope
1363 where the variable exists or a scope in stopScopes
1364 :param varName: variable name
1365 :param declStmt: declarative statment (will be used by addVar)
1366 :param pos: position of the variable in the list of dummy argument
1367 :param stopScopes: list of scopes to reach
1368 :param moduleVarList: list of module variable specification to insert in the xml code
1369 a module variable specification is a list of two elements:
1370 - module name
1371 - variable name or or list of variable names
1372 or None to add a USE statement without the ONLY attribute
1373 use moduleVarList to not add module variables
1374 :param otherNames: None or list of other variable names that can be used
1375 These variables are used first
1376 :param parserOptions, wrapH: see the PYFT class
1377
1378 Argument is inserted only on paths leading to one of scopes listed in stopScopes
1379 """
1380 def insertInArgList(varName, varNameToUse, pos, callFuncStmt):
1381 """
1382 Insert varName in the list of arguments to the subroutine or function call
1383 :param varName: name of the dummy argument
1384 :param varNameToUse: name of the variable
1385 :param pos: inclusion position
1386 :param callFuncStmt: call statement or function call
1387 """
1388 argList = callFuncStmt.find('./{*}R-LT/{*}parens-R/{*}element-LT')
1389 if argList is not None:
1390 container = createElem('element')
1391 else:
1392 argList = callFuncStmt.find('./{*}arg-spec')
1393 container = createElem('arg')
1394 if argList is None:
1395 # Call without argument
1396 callFuncStmt.find('./{*}procedure-designator').tail = '('
1397 argList = createElem('arg-spec', tail=')')
1398 callFuncStmt.append(argList)
1399 item = createExprPart(varNameToUse)
1400 previous = pos - 1 if pos >= 0 else len(argList) + pos # convert negative pos
1401 while previous >= 0 and tag(argList[previous]) in ('C', 'cnt'):
1402 previous -= 1
1403 following = pos if pos > 0 else len(argList) + pos + 1 # convert negative pos
1404 while following <= len(argList) - 1 and tag(argList[following]) in ('C', 'cnt'):
1405 following += 1
1406 if (previous >= 0 and argList[previous].find('./{*}arg-N/{*}k') is not None) or \
1407 (following <= len(argList) - 1 and
1408 argList[following].find('./{*}arg-N/{*}k') is not None) or \
1409 following == len(argList):
1410 # We use the key=val syntax whereever it is possible because it's safer in case of
1411 # optional arguments
1412 # If previous arg, or following arg is already with a key=val syntax, we can (must)
1413 # use it
1414 # If the inserted argument is the last one of the list, it also can use this syntax
1415 k = createElem('k', text=varName)
1416 argN = createElem('arg-N', tail='=')
1417 argN.append(k)
1418 argN.set('n', varName)
1419 container.append(argN)
1420 container.append(item)
1421 self.insertInList(pos, container, argList)
1422
1423 if self.path in stopScopes or self.tree.isUnderStopScopes(self.path, stopScopes):
1424 # We are on the path to a scope in the stopScopes list, or scopeUp is one of the
1425 # stopScopes
1426 var = self.varList.findVar(varName, exactScope=True)
1427 if otherNames is not None:
1428 vOther = [self.varList.findVar(v, exactScope=True) for v in otherNames]
1429 vOther = [v for v in vOther if v is not None]
1430 if len(vOther) > 0:
1431 var = vOther[-1]
1432
1433 if var is None:
1434 # The variable doesn't exist in this scope, we add it
1435 self.addVar([[self.path, varName, declStmt, pos]])
1436 if moduleVarList is not None:
1437 # Module variables must be added when var is added
1438 self.addModuleVar([(self.path, moduleName, moduleVarNames)
1439 for (moduleName, moduleVarNames) in moduleVarList])
1440
1441 # We look for interface declaration if subroutine is directly accessible
1442 if len(self.path.split('/')) == 1:
1443 filename, scopePathInterface = self.tree.findScopeInterface(self.path)
1444 if filename is not None:
1445 pft = None
1446 try:
1447 if self.getFileName() == os.path.normpath(filename):
1448 # interface declared in same file
1449 xml = self.mainScope
1450 pft = None
1451 else:
1453 filename, parserOptions, wrapH, tree=self.tree,
1454 clsPYFT=self._mainScope.__class__)
1455 xml = pft
1456 scopeInterface = xml.getScopeNode(scopePathInterface)
1457 varInterface = scopeInterface.varList.findVar(varName, exactScope=True)
1458 if varInterface is None:
1459 scopeInterface.addVar([[scopePathInterface, varName,
1460 declStmt, pos]])
1461 if moduleVarList is not None:
1462 # Module variables must be added when var is added
1463 xml.addModuleVar(
1464 [(scopePathInterface, moduleName, moduleVarNames)
1465 for (moduleName, moduleVarNames) in moduleVarList])
1466 if pft is not None:
1467 pft.write()
1468 finally:
1469 if pft is not None:
1470 pft.close()
1471
1472 if var is None and self.path not in stopScopes:
1473 # We must propagates upward
1474 # scopes calling the current scope
1475 for scopePathUp in self.tree.calledByScope(self.path):
1476 if scopePathUp in stopScopes or self.tree.isUnderStopScopes(scopePathUp,
1477 stopScopes):
1478 # We are on the path to a scope in the stopScopes list, or scopePathUp is
1479 # one of the stopScopes
1480 # can be defined several times?
1481 for filename in self.tree.scopeToFiles(scopePathUp):
1482 pft = None
1483 try:
1484 if self.getFileName() == os.path.normpath(filename):
1485 # Upper scope is in the same file
1486 xml = self.mainScope
1487 pft = None
1488 else:
1490 filename, parserOptions, wrapH,
1491 tree=self.tree,
1492 clsPYFT=self._mainScope.__class__)
1493 xml = pft
1494 scopeUp = xml.getScopeNode(scopePathUp)
1495 # Add the argument and propagate upward
1496 scopeUp.addArgInTree(
1497 varName, declStmt, pos,
1498 stopScopes, moduleVarList, otherNames,
1499 parserOptions=parserOptions,
1500 wrapH=wrapH)
1501 # Add the argument to calls (subroutine or function)
1502 name = self.path.split('/')[-1].split(':')[1].upper()
1503 isCalled = False
1504 varNameToUse = varName
1505 if otherNames is not None:
1506 vOther = [scopeUp.varList.findVar(v, exactScope=True)
1507 for v in otherNames]
1508 vOther = [v for v in vOther if v is not None]
1509 if len(vOther) > 0:
1510 varNameToUse = vOther[-1]['n']
1511 if self.path.split('/')[-1].split(':')[0] == 'sub':
1512 # We look for call statements
1513 for callStmt in scopeUp.findall('.//{*}call-stmt'):
1514 callName = n2name(callStmt.find(
1515 './{*}procedure-designator/{*}named-E/{*}N')).upper()
1516 if callName == name:
1517 insertInArgList(varName, varNameToUse, pos, callStmt)
1518 isCalled = True
1519 else:
1520 # We look for function use
1521 for funcCall in scopeUp.findall(
1522 './/{*}named-E/{*}R-LT/{*}parens-R/' +
1523 '{*}element-LT/../../..'):
1524 funcName = n2name(funcCall.find('./{*}N')).upper()
1525 if funcName == name:
1526 insertInArgList(varName, varNameToUse, pos, funcCall)
1527 isCalled = True
1528 if pft is not None:
1529 pft.write()
1530 finally:
1531 if pft is not None:
1532 pft.close()
1533
1534 if isCalled:
1535 # We must check in the scope (or upper scopes) if an interface
1536 # block declares the routine
1537 for interface in self.findall('.//{*}interface-construct/{*}' +
1538 'program-unit/{*}subroutine-stmt/' +
1539 '{*}subroutine-N/{*}N/../../../'):
1540 if n2name(interface.find('./{*}subroutine-stmt/' +
1541 '{*}subroutine-N/' +
1542 '{*}N')).upper() == name:
1543 # We must add the argument to the interface
1544 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:673
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
addArrayParenthesesInNode(self, node)
Definition variables.py:793
checkIntent(self, mustRaise=False)
Definition variables.py:349
modifyAutomaticArrays(self, declTemplate=None, startTemplate=None, endTemplate=None)
Definition variables.py:826
addExplicitArrayBounds(self, node=None)
Definition variables.py:693
_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)
Definition variables.py:989
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