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