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 not (var['arg'] or var['allocatable'] or
919 var['pointer'] or var['result'])]:
920 # For all automatic arrays, which are not argument, not allocatable,
921 # not pointer and not result
922 if var['init'] is None:
923 # Array with initial value can't be processed by modifyAutomaticArrays
924 varListToTransform.append(var)
925 # A variable can make use of the size of another variable in its declaration statement
926 # We order the variables to not insert the declaration of a variable before the
927 # declaration of the variables it depends on
928 orderedVarListToTransform = []
929 while len(varListToTransform) > 0:
930 nAdded = 0
931 for var in varListToTransform[:]:
932 # flatten var['asx'] excluding None
933 listN = [x for dim in var['asx'] for x in dim if x is not None]
934 listN = [n2name(nodeN).upper() for asx in listN
935 for nodeN in asx.findall('.//{*}N/{*}n/..')]
936 if len(set(listN).intersection([v['n'].upper()
937 for v in varListToTransform])) == 0:
938 # Variable var does not use variables still in varListToTransform
939 varListToTransform.remove(var)
940 orderedVarListToTransform.append(var)
941 nAdded += 1
942 if nAdded == 0:
943 raise PYFTError('It seems that there is a circular reference in ' +
944 'the declaration statements')
945 # Loop on variable to transform
946 for var in orderedVarListToTransform[::-1]: # reverse order
947 number += 1
948 # Apply the template
949 templ = copy.deepcopy(templates)
950 for templPart in templ:
951 if '{doubledotshape}' in templ[templPart]:
952 templ[templPart] = templ[templPart].replace(
953 '{doubledotshape}', ','.join([':'] * len(var['as'])))
954 if '{shape}' in templ[templPart]:
955 result = []
956 for i in range(len(var['as'])):
957 if var['as'][i][0] is None:
958 result.append(var['as'][i][1])
959 else:
960 result.append(var['as'][i][0] + ':' + var['as'][i][1])
961 templ[templPart] = templ[templPart].replace('{shape}', ', '.join(result))
962 if '{name}' in templ[templPart]:
963 templ[templPart] = templ[templPart].replace('{name}', var['n'])
964 if '{type}' in templ[templPart]:
965 templ[templPart] = templ[templPart].replace('{type}', var['t'])
966 if '{lowUpList}' in templ[templPart]:
967 result = []
968 for i in range(len(var['as'])):
969 if var['as'][i][0] is None:
970 result.extend([1, var['as'][i][1]])
971 else:
972 result.extend([var['as'][i][0], var['as'][i][1]])
973 templ[templPart] = templ[templPart].replace(
974 '{lowUpList}', ', '.join([str(r) for r in result]))
975
976 # Get the xml for the template
977 separator = "!ABCDEFGHIJKLMNOPQRSTUVWabcdefghijklmnopqrstuvwxyz0123456789"
978 part = 0
979 for node in createExpr(templ['decl'] + '\n' + separator + '\n' +
980 templ['start'] + '\n' + separator + '\n' +
981 templ['end']):
982 templPart = list(templ.keys())[part]
983 if not isinstance(templ[templPart], list):
984 templ[templPart] = []
985 if tag(node) == 'C' and node.text == separator:
986 part += 1
987 else:
988 templ[templPart].append(node)
989
990 # Replace declaration statement
991 # We look for the last position in the declaration list which do not use the
992 # variable we add
993 # This algorithm will stop when the original declaration statement is encoutered
994 for decl in scope.findall('./{*}T-decl-stmt'):
995 index = list(scope).index(decl)
996 if var['n'] in [n2name(nodeN) for nodeN in decl.findall('.//{*}N')]:
997 break
998 scope.removeVar([(scope.path, var['n'])], simplify=False)
999 for nnn in templ['decl'][::-1]:
1000 scope.insert(index, nnn)
1001
1002 # Insert statements
1003 for nnn in templ['start'][::-1]:
1004 scope.insertStatement(nnn, True)
1005 for nnn in templ['end'][::-1]:
1006 scope.insertStatement(nnn, False)
1007 return number
1008
1009 @staticmethod
1010 @debugDecor
1011 def varSpec2stmt(varSpec, implicitDeclaration=False):
1012 """
1013 :param varSpec: a variable description, same form as the items return by self.varList
1014 :param implicitDeclaration: True if the variable may contain implicit declaration
1015 (e.g. outside of PHYEX)
1016 :return: the associated declarative statement
1017 """
1018 if varSpec['use'] is not False:
1019 stmt = f"USE {varSpec['use']}, ONLY: {varSpec['n']}"
1020 else:
1021 stmt = varSpec['t']
1022 if varSpec['as']:
1023 stmt += ', DIMENSION('
1024 dl = []
1025 for el in varSpec['as']:
1026 if el[0] is None and el[1] is None:
1027 dl.append(':')
1028 elif el[0] is None:
1029 dl.append(el[1])
1030 else:
1031 dl.append(el[0] + ':' + el[1])
1032 if (varSpec['allocatable'] and not all(d == ':' for d in dl)) or \
1033 (any(d == ':' for d in dl) and not varSpec['allocatable']):
1034 if not implicitDeclaration:
1035 raise PYFTError('Missing dim are mandatory and allowed ' +
1036 'only for allocatable arrays')
1037 stmt += ', '.join(dl) + ')'
1038 if varSpec['allocatable']:
1039 stmt += ", ALLOCATABLE"
1040 if varSpec['parameter']:
1041 stmt += ", PARAMETER"
1042 if varSpec['i'] is not None:
1043 stmt += ", INTENT(" + varSpec['i'] + ")"
1044 if varSpec['opt'] is True:
1045 stmt += ", OPTIONAL"
1046 stmt += " :: " + varSpec['n']
1047 if varSpec['init'] is not None:
1048 stmt += "=" + varSpec['init']
1049 return stmt
1050
1051 @debugDecor
1052 def findIndexArrayBounds(self, arr, index, loopVar):
1053 """
1054 Find bounds and loop variable for a given array index
1055 :param arr: array node (named-E node with a array-R child)
1056 :param index: index of the rank of the array
1057 :param loopVar: None to create new variable for each added DO loop
1058 or a function that return the name of the variable to use for the
1059 loop control.
1060 This function returns a string (name of the variable), or True to create
1061 a new variable, or False to not transform this statement
1062 The functions takes as arguments:
1063 - lower and upper bounds as defined in the declaration statement
1064 - lower and upper bounds as given in the statement
1065 - name of the array
1066 - index of the rank
1067 :return: the tuple (loopName, lowerBound, upperBound) where:
1068 loopName is the name of the variable to use for the loop
1069 lower and upper bounds are the bounds to use for the DO loop
1070 loopName can be:
1071 - a string
1072 - False to discard this index
1073 - True to create a new variable to loop with
1074 """
1075 name = n2name(arr.find('./{*}N'))
1076 ss = arr.findall('./{*}R-LT/{*}array-R/{*}section-subscript-LT/{*}section-subscript')[index]
1077 # We are only interested by the subscript containing ':'
1078 # we must not iterate over the others, eg: X(:,1)
1079 if ':' in alltext(ss):
1080 # Look for lower and upper bounds for iteration and declaration
1081 lowerUsed = ss.find('./{*}lower-bound')
1082 upperUsed = ss.find('./{*}upper-bound')
1083 varDesc = self.varList.findVar(name, array=True)
1084 if varDesc is not None:
1085 lowerDecl, upperDecl = varDesc['as'][index]
1086 if lowerDecl is None:
1087 lowerDecl = '1' # default lower index for FORTRAN arrays
1088 else:
1089 lowerDecl, upperDecl = None, None
1090
1091 # name of the loop variable
1092 if loopVar is None:
1093 # loopVar is not defined, we create a new variable for the loop only if lower
1094 # and upper bounds have been found (easy way to discard character strings)
1095 varName = lowerDecl is not None and upperDecl is not None
1096 else:
1097 varName = loopVar(lowerDecl, upperDecl,
1098 None if lowerUsed is None else alltext(lowerUsed),
1099 None if upperUsed is None else alltext(upperUsed), name, index)
1100 return (varName,
1101 lowerDecl if lowerUsed is None else alltext(lowerUsed),
1102 upperDecl if upperUsed is None else alltext(upperUsed))
1103 return None
1104
1105 @debugDecor
1106 def arrayR2parensR(self, namedE, table):
1107 """
1108 Transform a array-R into a parens-R node by replacing slices by variables
1109 In 'A(:)', the ':' is in a array-R node whereas in 'A(JL)', 'JL' is in a parens-R node.
1110 Both the array-R and the parens-R nodes are inside a R-LT node
1111 :param namedE: a named-E node
1112 :param table: dictionnary returned by the decode function
1113 :param varList: None or a VarList object in which varaibles are searched for
1114 """
1115 # Before A(:): <f:named-E>
1116 # <f:N><f:n>A</f:n></f:N>
1117 # <f:R-LT>
1118 # <f:array-R>(
1119 # <f:section-subscript-LT>
1120 # <f:section-subscript>:</f:section-subscript>
1121 # </f:section-subscript-LT>)
1122 # </f:array-R>
1123 # </f:R-LT>
1124 # </f:named-E>
1125 # After A(I): <f:named-E>
1126 # <f:N><f:n>A</f:n></f:N>
1127 # <f:R-LT>
1128 # <f:parens-R>(
1129 # <f:element-LT>
1130 # <f:element><f:named-E><f:N><f:n>I</f:n></f:N></f:named-E></f:element>
1131 # </f:element-LT>)
1132 # </f:parens-R>
1133 # </f:R-LT>
1134 # </f:named-E>
1135
1136 nodeRLT = namedE.find('./{*}R-LT')
1137 arrayR = nodeRLT.find('./{*}array-R') # Not always in first position, eg: ICED%XRTMIN(:)
1138 if arrayR is not None:
1139 index = list(nodeRLT).index(arrayR)
1140 parensR = createElem('parens-R', text='(', tail=')')
1141 elementLT = createElem('element-LT')
1142 parensR.append(elementLT)
1143 ivar = -1
1144 for ss in nodeRLT[index].findall('./{*}section-subscript-LT/{*}section-subscript'):
1145 element = createElem('element', tail=', ')
1146 elementLT.append(element)
1147 if ':' in alltext(ss):
1148 ivar += 1
1149 varName = list(table.keys())[ivar] # variable name
1150 lower = ss.find('./{*}lower-bound')
1151 upper = ss.find('./{*}upper-bound')
1152 if lower is not None:
1153 lower = alltext(lower)
1154 if upper is not None:
1155 upper = alltext(upper)
1156 if lower is not None and ss.text is not None and ':' in ss.text:
1157 # fxtran bug workaround
1158 upper = lower
1159 lower = None
1160 if lower is None and upper is None:
1161 # E.g. 'A(:)'
1162 # In this case we use the DO loop bounds without checking validity with
1163 # respect to the array declared bounds
1164 element.append(createExprPart(varName))
1165 else:
1166 # E.g.:
1167 # !$mnh_expand_array(JI=2:15)
1168 # A(2:15) or A(:15) or A(2:)
1169 if lower is None:
1170 # lower bound not defined, getting lower declared bound for this array
1171 lower = self.varList.findVar(n2name(namedE.find('{*}N')),
1172 array=True)['as'][ivar][0]
1173 if lower is None:
1174 lower = '1' # default fortran lower bound
1175 elif upper is None:
1176 # upper bound not defined, getting upper declared bound for this array
1177 upper = self.varList.findVar(n2name(namedE.find('{*}N')),
1178 array=True)['as'][ivar][1]
1179 # If the DO loop starts from JI=I1 and goes to JI=I2; and array
1180 # bounds are J1:J2
1181 # We compute J1-I1+JI and J2-I2+JI and they should be the same
1182 # E.g: array bounds could be 'I1:I2' (becoming JI:JI) or 'I1+1:I2+1"
1183 # (becoming JI+1:JI+1)
1184 newlower = simplifyExpr(lower, add=varName, sub=table[varName][0])
1185 newupper = simplifyExpr(upper, add=varName, sub=table[varName][1])
1186 if newlower != newupper:
1187 raise PYFTError(("Don't know how to do with an array declared with " +
1188 "'{la}:{ua}' and a loop from '{ll}' to '{ul}'"
1189 ).format(la=lower, ua=upper,
1190 ll=table[varName][0],
1191 ul=table[varName][1]))
1192 element.append(createExprPart(newlower))
1193 else:
1194 element.append(ss.find('./{*}lower-bound'))
1195 element.tail = None # last element
1196 nodeRLT.remove(nodeRLT[index])
1197 nodeRLT.insert(index, parensR)
1198
1199 @debugDecor
1200 def findArrayBounds(self, arr, loopVar, extraVarList=None):
1201 """
1202 Find bounds and loop variable given an array
1203 :param arr: array node (named-E node with a array-R child)
1204 :param loopVar: None to create new variable for each added DO loop
1205 or a function that return the name of the variable to use for the loop
1206 control.
1207 This function returns a string (name of the variable), or True to create
1208 a new variable, or False to not transform this statement
1209 The functions takes as arguments:
1210 - lower and upper bounds as defined in the declaration statement
1211 - lower and upper bounds as given in the statement
1212 - name of the array
1213 - index of the rank
1214 :param extraVarList: None or list of variables (such as those contained in a VarList object)
1215 defined but not yet available in the self.varList object.
1216 :return: the tuple (table, newVar) where:
1217 table is a dictionnary: keys are loop variable names
1218 values are tuples with lower and upper bounds
1219 newVar is a list of loop variables not found in varList. This list has the same
1220 format as the varList list.
1221
1222 In case the loop variable cannot be defined, the function returns (None, [])
1223 """
1224 table = {} # ordered since python 3.7
1225 name = n2name(arr.find('./{*}N'))
1226 varNew = []
1227 extraVarList = extraVarList if extraVarList is not None else []
1228
1229 # Iteration on the different subscript
1230 for iss, ss in enumerate(arr.findall('./{*}R-LT/{*}array-R/{*}section-subscript-LT/' +
1231 '{*}section-subscript')):
1232 # We are only interested by the subscript containing ':'
1233 # we must not iterate over the others, eg: X(:,1)
1234 if ':' in alltext(ss):
1235 # Look for loop variable name and lower/upper bounds for iteration
1236 varName, lower, upper = self.findIndexArrayBounds(arr, iss, loopVar)
1237 # varName can be a string (name to use), True (to create a variable),
1238 # False (to discard the array
1239 if varName is not False and varName in table:
1240 raise PYFTError(("The variable {var} must be used for the rank #{i1} whereas " +
1241 "it is already used for rank #{i2} (for array {name})."
1242 ).format(var=varName, i1=str(iss),
1243 i2=str(list(table.keys()).index(varName)),
1244 name=name))
1245 if varName is True:
1246 # A new variable must be created
1247 # We look for a variable name that don't already exist
1248 # We can reuse a newly created varaible only if it is not used for the previous
1249 # indexes of the same statement
1250 j = 0
1251 found = False
1252 while not found:
1253 j += 1
1254 varName = 'J' + str(j)
1255 var = self.varList.findVar(varName, extraVarList=extraVarList + varNew)
1256 if (var is None or var.get('new', False)) and varName not in table:
1257 found = True
1258 varDesc = {'as': [], 'asx': [], 'n': varName, 'i': None,
1259 't': 'INTEGER', 'arg': False, 'use': False, 'opt': False,
1260 'scopePath': self.path}
1261 if varDesc not in varNew:
1262 varNew.append(varDesc)
1263
1264 elif (varName is not False and
1265 self.varList.findVar(varName, array=False, exactScope=True) is None):
1266 # We must declare the variable
1267 varDesc = {'as': [], 'asx': [], 'n': varName, 'i': None,
1268 't': 'INTEGER', 'arg': False, 'use': False, 'opt': False,
1269 'scopePath': self.path}
1270 varNew.append(varDesc)
1271
1272 # fill table
1273 table[varName] = (lower, upper)
1274
1275 return (None, []) if False in table else (table, varNew)
1276
1277 @debugDecor
1278 @updateVarList
1279 def renameVar(self, oldName, newName):
1280 """
1281 :param oldName: old name of the variable
1282 :param newName: new name of the variable
1283 """
1284 for node in self.findall('.//{*}N'):
1285 if n2name(node).upper() == oldName.upper():
1286 # Remove all n tag but one
1287 for nnn in node.findall('./{*}n')[1:]:
1288 node.remove(nnn)
1289 # Fill the first n with the new name
1290 node.find('./{*}n').text = newName
1291
1292 @debugDecor
1293 def removeVarIfUnused(self, varList, excludeDummy=False, excludeModule=False, simplify=False):
1294 """
1295 :param varList: list of variables to remove if unused. Each item is a list or tuple of two
1296 elements.
1297 The first one describes where the variable is used, the second one is
1298 the name of the variable. The first element is a '/'-separated path with
1299 each element having the form 'module:<name of the module>',
1300 'sub:<name of the subroutine>' or 'func:<name of the function>'
1301 :param excludeDummy: if True, dummy arguments are always kept untouched
1302 :param excludeModule: if True, module variables are always kept untouched
1303 :param simplify: try to simplify code (if we delete a declaration statement that used a
1304 variable as kind selector, and if this variable is not used else where,
1305 we also delete it)
1306 :return: the varList without the unremovable variables
1307 If possible, remove the variable from declaration, and from the argument list if needed
1308 """
1309 varList = self._normalizeUniqVar(varList)
1310 if excludeModule:
1311 varList = [v for v in varList if v[0].split('/')[-1].split(':')[0] != 'module']
1312
1313 varUsed = self.isVarUsed(varList, dummyAreAlwaysUsed=excludeDummy)
1314 varListToRemove = []
1315 for scopePath, varName in varList:
1316 assert scopePath.split('/')[-1].split(':')[0] != 'type', \
1317 "The removeVarIfUnused cannot be used with type members"
1318 if not varUsed[(scopePath, varName)]:
1319 varListToRemove.append([scopePath, varName])
1320 self.removeVar(varListToRemove, simplify=simplify)
1321 return varListToRemove
1322
1323 @debugDecor
1324 def isVarUsed(self, varList, exactScope=False, dummyAreAlwaysUsed=False):
1325 """
1326 :param varList: list of variables to test. Each item is a list or tuple of two elements.
1327 The first one describes where the variable is declared, the second one is
1328 the name of the variable. The first element is a '/'-separated path with
1329 each element having the form 'module:<name of the module>',
1330 'sub:<name of the subroutine>' or 'func:<name of the function>'
1331 :param exactScope: True to search strictly in scope
1332 :param dummyAreAlwaysUsed: Returns True if variable is a dummy argument
1333 :return: a dict whose keys are the elements of varList, and values are True when the
1334 variable is used, False otherwise
1335
1336 If exactScope is True, the function will search for variable usage
1337 only in this scope. But this feature has a limited interest.
1338
1339 If exactScope is False:
1340 - if scopePath is a subroutine/function in a contains section,
1341 and if the variable is not declared in this scope, usages are
1342 searched in the module/subroutine/function upper that declared
1343 the variable and in all subroutines/functions in the contains section
1344 - if scopePath is a module/subroutine/function that has a
1345 contains sections, usages are searched in all subroutines/functions
1346 in the contains section
1347
1348 To know if a variable can be removed, you must use exactScope=False
1349 """
1350 varList = self._normalizeUniqVar(varList)
1351 # We must not limit to self.getScopes because var can be used upper than self
1352 allScopes = {scope.path: scope for scope in self.mainScope.getScopes()}
1353
1354 # Computes in which scopes variable must be searched
1355 if exactScope:
1356 locsVar = {(scopePath, varName): [scopePath]
1357 for scopePath, varName in varList}
1358 else:
1359 locsVar = {}
1360 for scopePath, varName in varList:
1361 # We search everywhere if var declaration is not found
1362 # Otherwise, we search from the scope where the variable is declared
1363 var = allScopes[scopePath].varList.findVar(varName)
1364 path = scopePath.split('/')[0] if var is None else var['scopePath']
1365
1366 # We start search from here but we must include all routines in contains
1367 # that do not declare again the same variable name
1368 testScopes = [path] # we must search in the current scope
1369 for scPath, sc in allScopes.items():
1370 if scPath.startswith(path + '/') and \
1371 scPath.split('/')[-1].split(':')[0] != 'type':
1372 # sc is a scope path contained inside path and is not a type declaration
1373 if sc.varList.findVar(varName, exactScope=True) is None:
1374 # There is not another variable with same name declared inside
1375 testScopes.append(scPath) # if variable is used here, it is used
1376 locsVar[(scopePath, varName)] = testScopes
1377
1378 # For each scope to search, list all the variables used
1379 usedVar = {}
1380 for scopePath in list(set(item for sublist in locsVar.values() for item in sublist)):
1381 usedVar[scopePath] = []
1382 # Loop on all child in the scope
1383 for node in allScopes[scopePath]:
1384 # we don't want use statement, it could be where the variable is declared,
1385 # not a usage place
1386 if not tag(node) == 'use-stmt':
1387 if tag(node) == 'T-decl-stmt':
1388 # We don't want the part with the list of declared variables, we only want
1389 # to capture variables used in the kind selector or in the shape
1390 # specification
1391 nodesN = node.findall('.//{*}_T-spec_//{*}N') + \
1392 node.findall('.//{*}shape-spec//{*}N')
1393 else:
1394 nodesN = node.findall('.//{*}N')
1395
1396 # We look for the variable name in these 'N' nodes.
1397 for nodeN in nodesN:
1398 if dummyAreAlwaysUsed:
1399 # No need to if the variable is a dummy argument; because if it is
1400 # one it will be found in the argument list of the subroutine/function
1401 # and will be considered as used
1402 usedVar[scopePath].append(n2name(nodeN).upper())
1403 else:
1404 parPar = allScopes[scopePath].getParent(nodeN, 2) # parent of parent
1405 # We exclude dummy argument list to really check if the variable is used
1406 # and do not only appear as an argument of the subroutine/function
1407 if parPar is None or not tag(parPar) == 'dummy-arg-LT':
1408 usedVar[scopePath].append(n2name(nodeN).upper())
1409
1410 result = {}
1411 for scopePath, varName in varList:
1412 assert scopePath.split('/')[-1].split(':')[0] != 'type', \
1413 'We cannot check type component usage'
1414 result[(scopePath, varName)] = any(varName.upper() in usedVar[scopePath]
1415 for scopePath in locsVar[(scopePath, varName)])
1416
1417 return result
1418
1419 @debugDecor
1420 @updateVarList
1421 def addArgInTree(self, varName, declStmt, pos, stopScopes, moduleVarList=None,
1422 otherNames=None,
1423 parserOptions=None, wrapH=False):
1424 """
1425 Adds an argument to the routine and propagates it upward until we encounter a scope
1426 where the variable exists or a scope in stopScopes
1427 :param varName: variable name
1428 :param declStmt: declarative statment (will be used by addVar)
1429 :param pos: position of the variable in the list of dummy argument
1430 :param stopScopes: list of scopes to reach
1431 :param moduleVarList: list of module variable specification to insert in the xml code
1432 a module variable specification is a list of two elements:
1433 - module name
1434 - variable name or or list of variable names
1435 or None to add a USE statement without the ONLY attribute
1436 use moduleVarList to not add module variables
1437 :param otherNames: None or list of other variable names that can be used
1438 These variables are used first
1439 :param parserOptions, wrapH: see the PYFT class
1440
1441 Argument is inserted only on paths leading to one of scopes listed in stopScopes
1442 """
1443 def insertInArgList(varName, varNameToUse, pos, callFuncStmt):
1444 """
1445 Insert varName in the list of arguments to the subroutine or function call
1446 :param varName: name of the dummy argument
1447 :param varNameToUse: name of the variable
1448 :param pos: inclusion position
1449 :param callFuncStmt: call statement or function call
1450 """
1451 argList = callFuncStmt.find('./{*}R-LT/{*}parens-R/{*}element-LT')
1452 if argList is not None:
1453 container = createElem('element')
1454 else:
1455 argList = callFuncStmt.find('./{*}arg-spec')
1456 container = createElem('arg')
1457 if argList is None:
1458 # Call without argument
1459 callFuncStmt.find('./{*}procedure-designator').tail = '('
1460 argList = createElem('arg-spec', tail=')')
1461 callFuncStmt.append(argList)
1462 item = createExprPart(varNameToUse)
1463 previous = pos - 1 if pos >= 0 else len(argList) + pos # convert negative pos
1464 while previous >= 0 and tag(argList[previous]) in ('C', 'cnt'):
1465 previous -= 1
1466 following = pos if pos > 0 else len(argList) + pos + 1 # convert negative pos
1467 while following <= len(argList) - 1 and tag(argList[following]) in ('C', 'cnt'):
1468 following += 1
1469 if (previous >= 0 and argList[previous].find('./{*}arg-N/{*}k') is not None) or \
1470 (following <= len(argList) - 1 and
1471 argList[following].find('./{*}arg-N/{*}k') is not None) or \
1472 following == len(argList):
1473 # We use the key=val syntax whereever it is possible because it's safer in case of
1474 # optional arguments
1475 # If previous arg, or following arg is already with a key=val syntax, we can (must)
1476 # use it
1477 # If the inserted argument is the last one of the list, it also can use this syntax
1478 k = createElem('k', text=varName)
1479 argN = createElem('arg-N', tail='=')
1480 argN.append(k)
1481 argN.set('n', varName)
1482 container.append(argN)
1483 container.append(item)
1484 self.insertInList(pos, container, argList)
1485
1486 if self.path in stopScopes or self.tree.isUnderStopScopes(self.path, stopScopes):
1487 # We are on the path to a scope in the stopScopes list, or scopeUp is one of the
1488 # stopScopes
1489 var = self.varList.findVar(varName, exactScope=True)
1490 if otherNames is not None:
1491 vOther = [self.varList.findVar(v, exactScope=True) for v in otherNames]
1492 vOther = [v for v in vOther if v is not None]
1493 if len(vOther) > 0:
1494 var = vOther[-1]
1495
1496 if var is None:
1497 # The variable doesn't exist in this scope, we add it
1498 self.addVar([[self.path, varName, declStmt, pos]])
1499 if moduleVarList is not None:
1500 # Module variables must be added when var is added
1501 self.addModuleVar([(self.path, moduleName, moduleVarNames)
1502 for (moduleName, moduleVarNames) in moduleVarList])
1503
1504 # We look for interface declaration if subroutine is directly accessible
1505 if len(self.path.split('/')) == 1:
1506 filename, scopePathInterface = self.tree.findScopeInterface(self.path)
1507 if filename is not None:
1508 pft = None
1509 try:
1510 if self.getFileName() == os.path.normpath(filename):
1511 # interface declared in same file
1512 xml = self.mainScope
1513 pft = None
1514 else:
1516 filename, parserOptions, wrapH, tree=self.tree,
1517 clsPYFT=self._mainScope.__class__)
1518 xml = pft
1519 scopeInterface = xml.getScopeNode(scopePathInterface)
1520 varInterface = scopeInterface.varList.findVar(varName, exactScope=True)
1521 if varInterface is None:
1522 scopeInterface.addVar([[scopePathInterface, varName,
1523 declStmt, pos]])
1524 if moduleVarList is not None:
1525 # Module variables must be added when var is added
1526 xml.addModuleVar(
1527 [(scopePathInterface, moduleName, moduleVarNames)
1528 for (moduleName, moduleVarNames) in moduleVarList])
1529 if pft is not None:
1530 pft.write()
1531 finally:
1532 if pft is not None:
1533 pft.close()
1534
1535 if var is None and self.path not in stopScopes:
1536 # We must propagates upward
1537 # scopes calling the current scope
1538 for scopePathUp in self.tree.calledByScope(self.path):
1539 if scopePathUp in stopScopes or self.tree.isUnderStopScopes(scopePathUp,
1540 stopScopes):
1541 # We are on the path to a scope in the stopScopes list, or scopePathUp is
1542 # one of the stopScopes
1543 # can be defined several times?
1544 for filename in self.tree.scopeToFiles(scopePathUp):
1545 pft = None
1546 try:
1547 if self.getFileName() == os.path.normpath(filename):
1548 # Upper scope is in the same file
1549 xml = self.mainScope
1550 pft = None
1551 else:
1553 filename, parserOptions, wrapH,
1554 tree=self.tree,
1555 clsPYFT=self._mainScope.__class__)
1556 xml = pft
1557 scopeUp = xml.getScopeNode(scopePathUp)
1558 # Add the argument and propagate upward
1559 scopeUp.addArgInTree(
1560 varName, declStmt, pos,
1561 stopScopes, moduleVarList, otherNames,
1562 parserOptions=parserOptions,
1563 wrapH=wrapH)
1564 # Add the argument to calls (subroutine or function)
1565 name = self.path.split('/')[-1].split(':')[1].upper()
1566 isCalled = False
1567 varNameToUse = varName
1568 if otherNames is not None:
1569 vOther = [scopeUp.varList.findVar(v, exactScope=True)
1570 for v in otherNames]
1571 vOther = [v for v in vOther if v is not None]
1572 if len(vOther) > 0:
1573 varNameToUse = vOther[-1]['n']
1574 if self.path.split('/')[-1].split(':')[0] == 'sub':
1575 # We look for call statements
1576 for callStmt in scopeUp.findall('.//{*}call-stmt'):
1577 callName = n2name(callStmt.find(
1578 './{*}procedure-designator/{*}named-E/{*}N')).upper()
1579 if callName == name:
1580 insertInArgList(varName, varNameToUse, pos, callStmt)
1581 isCalled = True
1582 else:
1583 # We look for function use
1584 for funcCall in scopeUp.findall(
1585 './/{*}named-E/{*}R-LT/{*}parens-R/' +
1586 '{*}element-LT/../../..'):
1587 funcName = n2name(funcCall.find('./{*}N')).upper()
1588 if funcName == name:
1589 insertInArgList(varName, varNameToUse, pos, funcCall)
1590 isCalled = True
1591 if pft is not None:
1592 pft.write()
1593 finally:
1594 if pft is not None:
1595 pft.close()
1596
1597 if isCalled:
1598 # We must check in the scope (or upper scopes) if an interface
1599 # block declares the routine
1600 for interface in self.findall('.//{*}interface-construct/{*}' +
1601 'program-unit/{*}subroutine-stmt/' +
1602 '{*}subroutine-N/{*}N/../../../'):
1603 if n2name(interface.find('./{*}subroutine-stmt/' +
1604 '{*}subroutine-N/' +
1605 '{*}N')).upper() == name:
1606 # We must add the argument to the interface
1607 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