2Variable management and declaration manipulation.
4Provides the VarList and Variables classes for managing FORTRAN variable
5declarations and characteristics.
9- Variable declaration parsing and analysis
10- Array specification management (attach/detach dimensions)
11- Automatic array transformation (stack to heap allocation)
12- Unused variable detection and removal
13- Module and dummy argument handling
17VarList : Manages a list of variables for a scope
18Variables : Mixin class providing variable manipulation methods
22>>> pft = PYFT('input.F90')
23>>> pft.varList.findVar('X')
24{'n': 'X', 't': 'REAL', 'as': [], 'i': None, ...}
25>>> pft.attachArraySpecToEntity()
26>>> pft.modifyAutomaticArrays(
27 declTemplate="{type}, DIMENSION({doubledotshape}), ALLOCATABLE :: {name}",
28 startTemplate="ALLOCATE({name}({shape}))",
29 endTemplate="DEALLOCATE({name})")
35from functools
import wraps
38from pyfortool.util import PYFTError, debugDecor, alltext, isExecutable, n2name, tag, noParallel
40 createExpr, createElem)
48 Get the declaration statement tag for a given scope path.
53 A scope path (e.g., 'module:MOD/sub:SUB' or 'type:MYTYPE').
58 The XML tag for declaration statements:
59 - 'component-decl-stmt' for type declarations
60 - 'T-decl-stmt' for other scopes (module, subroutine, function)
62 if scopePath.split(
'/')[-1].split(
':')[0] ==
'type':
63 declStmt =
'component-decl-stmt'
65 declStmt =
'T-decl-stmt'
69def updateVarList(func):
71 Decorator to invalidate the variable list cache.
73 Signals that the variable list needs to be recomputed after
74 a modification to the code tree.
77 def wrapper(self, *args, **kwargs):
78 result = func(self, *args, **kwargs)
79 self.mainScope._varList =
None
86 Store the characteristics of all variables contained in a source code.
88 This class provides methods to query, search, and manage variables
89 declared in a FORTRAN scope (module, subroutine, function, or type).
94 List of variable descriptors for the current scope.
96 Complete list of variable descriptors for the whole file.
98 Path of the current scope (e.g., 'module:MODULE/sub:SUB').
102 - Variables are found in modules only if the 'ONLY' attribute is used.
103 - Array specification and type are unknown for module variables.
104 - 'ASSOCIATE' statements are not followed.
108 >>> pft = PYFT('myfile.F90')
111 {'n': 'X', 't': 'REAL', 'as': [(None, '10')], 'i': None, ...}
112 >>> vl.findVar('Y', array=True) # Search only arrays
113 {'n': 'Y', 't': 'REAL', 'as': [(None, '100'), (None, '100')], ...}
117 :param mainScope: a PYFT object
118 :param _preCompute: pre-computed values (_varList, _fullVarList, _scopePath)
120 Notes: - variables are found in modules only if the 'ONLY' attribute is used
121 - array specification and type is unknown for module variables
122 - 'ASSOCIATE' statements are not followed
130 if _preCompute
is None:
135 assert mainScope
is None
138 def __getitem__(self, *args, **kwargs):
139 return self.
_varList.__getitem__(*args, **kwargs)
141 def __setitem__(self, *args, **kwargs):
142 return self.
_varList.__setitem__(*args, **kwargs)
144 def __delitem__(self, *args, **kwargs):
145 return self.
_varList.__delitem__(*args, **kwargs)
147 def __len__(self, *args, **kwargs):
148 return self.
_varList.__len__(*args, **kwargs)
153 :param mainScope: a PYFT object
155 def decodeArraySpecs(arraySpecs):
158 for arraySpec
in arraySpecs:
159 lb = arraySpec.find(
'.//{*}lower-bound')
160 ub = arraySpec.find(
'.//{*}upper-bound')
161 asList.append([alltext(lb)
if lb
is not None else None,
162 alltext(ub)
if ub
is not None else None])
163 asxList.append([lb
if lb
is not None else None, ub
if ub
is not None else None])
164 return asList, asxList
167 for scope
in mainScope.getScopes():
169 if tag(scope[0]) ==
'function-stmt':
170 rSpec = scope[0].find(
'./{*}result-spec/{*}N')
171 funcResultName = rSpec
if rSpec
is not None else \
172 scope[0].find(
'./{*}function-N/{*}N')
173 funcResultName = n2name(funcResultName).upper()
178 dummyArgs = [n2name(e).upper()
for stmt
in scope
179 for e
in stmt.findall(
'.//{*}dummy-arg-LT/{*}arg-N/{*}N')]
182 for declStmt
in [stmt
for stmt
in scope
183 if tag(stmt)
in (
'T-decl-stmt' 'component-decl-stmt')]:
184 tSpec = alltext(declStmt.find(
'.//{*}_T-spec_'))
185 iSpec = declStmt.find(
'.//{*}intent-spec')
186 if iSpec
is not None:
192 allattributes = declStmt.findall(
'.//{*}attribute/{*}attribute-N')
193 for attribute
in allattributes:
194 if alltext(attribute).upper() ==
'OPTIONAL':
196 if alltext(attribute).upper() ==
'ALLOCATABLE':
198 if alltext(attribute).upper() ==
'PARAMETER':
200 if alltext(attribute).upper() ==
'POINTER':
203 arraySpecs = declStmt.findall(
'.//{*}attribute//{*}array-spec//{*}shape-spec')
204 as0List, asx0List = decodeArraySpecs(arraySpecs)
207 for enDecl
in declStmt.findall(
'.//{*}EN-decl'):
208 varName = n2name(enDecl.find(
'.//{*}N')).upper()
210 arraySpecs = enDecl.findall(
'.//{*}array-spec//{*}shape-spec')
211 asList, asxList = decodeArraySpecs(arraySpecs)
213 init = enDecl.find(
'./{*}init-E')
217 argorder = dummyArgs.index(varName)
if varName
in dummyArgs
else None
218 result.append({
'as': asList
if len(as0List) == 0
else as0List,
219 'asx': asxList
if len(asx0List) == 0
else asx0List,
220 'n': varName,
'i': iSpec,
't': tSpec,
221 'arg': varName
in dummyArgs,
222 'argorder': argorder,
223 'use':
False,
'opt': optSpec,
'allocatable': allocatable,
224 'parameter': parameter,
'pointer': pointer,
225 'result': funcResultName == varName,
226 'init': init,
'scopePath': scope.path})
229 for useStmt
in [stmt
for stmt
in scope
if tag(stmt) ==
'use-stmt']:
230 module = n2name(useStmt.find(
'.//{*}module-N').find(
'.//{*}N'))
231 for var
in useStmt.findall(
'.//{*}use-N'):
232 varName = n2name(var.find(
'.//{*}N'))
233 result.append({
'as':
None,
'asx':
None,
234 'n': varName,
'i':
None,
't':
None,
'arg':
False,
236 'use': module,
'opt':
None,
'allocatable':
None,
237 'parameter':
None,
'pointer':
None,
'result':
None,
238 'init':
None,
'scopePath': scope.path})
243 Return a VarList restricted to a specific scope.
248 Scope path to restrict to (e.g., 'module:MOD/sub:SUB').
249 Use empty string or '/' for root scope.
250 excludeContains : bool
251 If True, exclude variables declared in CONTAINS sections
252 (nested subroutines/functions).
257 New VarList instance restricted to the specified scope.
262 >>> sub_vl = vl.restrict('module:MOD/sub:SUB', excludeContains=True)
263 >>> sub_vl.findVar('X')
266 scopePath =
'' if scopePath ==
'/' else scopePath
267 root = scopePath +
'/' if scopePath ==
'/' else scopePath
268 varList = [item
for item
in self.
_varList
269 if item[
'scopePath'] == scopePath
or
270 (item[
'scopePath'].startswith(root)
and
271 not excludeContains)]
275 def findVar(self, varName, array=None, exactScope=False, extraVarList=None):
277 Search for a variable in the list of declared variables.
282 Name of the variable to search for.
283 array : bool, optional
284 True to limit search to arrays only,
285 False to limit search to non-array (scalar) variables only,
286 None (default) to return any variable type.
287 exactScope : bool, optional
288 If True, only search for variables declared in the current scope path.
289 If False (default), search in current scope and all parent scopes.
290 extraVarList : list, optional
291 Additional list of variable descriptors to search in.
292 Useful when variables are defined but not yet in varList.
297 Variable descriptor dictionary with keys:
298 - 'n': variable name (uppercase)
299 - 't': type specification (e.g., 'REAL', 'INTEGER')
300 - 'as': array specification as list of (lower, upper) tuples
301 - 'i': intent specification (e.g., 'IN', 'OUT', 'INOUT')
302 - 'arg': True if dummy argument
303 - 'scopePath': path where variable is declared
304 - 'use': module name if imported via USE statement
305 Returns None if variable not found.
309 The function returns the declaration of the variable found in the
310 deepest (most specific) scope. If `array` is specified, only
311 returns the variable if its declaration matches the expected kind.
316 >>> vl.findVar('X') # Find any variable named X
317 {'n': 'X', 't': 'REAL', 'as': [], 'i': None, ...}
318 >>> vl.findVar('Y', array=True) # Find array Y
319 {'n': 'Y', 't': 'REAL', 'as': [(None, '100')], ...}
320 >>> vl.findVar('Z', array=False) # Find scalar Z
321 {'n': 'Z', 't': 'INTEGER', 'as': [], ...}
322 >>> vl.findVar('P', exactScope=True) # Only in current scope
323 {'n': 'P', 't': 'REAL', ...}
325 extraVarList = extraVarList
if extraVarList
is not None else []
328 candidates = {v[
'scopePath']: v
for v
in self.
_fullVarList + extraVarList
329 if v[
'n'].upper() == varName.upper()
and
331 (self.
_scopePath.startswith(v[
'scopePath'] +
'/')
and
333 if len(candidates) > 0:
334 last = candidates[max(candidates, key=len)]
335 if array
is True and last.get(
'as',
None)
is not None and len(last[
'as']) > 0:
337 if array
is False and len(last.get(
'as', [])) == 0:
346 Display a formatted list of all variables.
348 Prints detailed information about each variable including:
350 - Whether it's scalar or array (with dimensions)
351 - Whether it's a dummy argument (with intent) or local variable
352 - Module origin for imported variables
356 >>> pft = PYFT('input.F90')
357 >>> pft.varList.showVarList()
358 List of variables declared in /module:MOD/sub:SUB:
361 is a dummy argument with intent IN
363 is of rank 2, with dimensions (:,1:10)
366 for sc
in set(v[
'scopePath']
for v
in self.
_varList):
367 print(f
'List of variables declared in {sc}:')
368 for var
in [var
for var
in self.
_varList if var[
'scopePath'] == sc]:
369 print(f
" Variable {var['n']}:")
371 print(f
" is a variable taken in the {var['use']} module")
373 isscalar = len(var[
'as']) == 0
377 print(
' is of rank {}, with dimensions {}'.format(len(var[
'as']),
378 ', '.join([(
':'.join([(
'' if s
is None else s)
379 for s
in var[
'as'][i]]))
380 for i
in range(len(var[
'as']))])))
382 intent =
'without intent' if var[
'i']
is None else \
383 f
"with intent {var['i']}"
384 print(f
' is a dummy argument {intent}')
386 print(
' is a local variable')
392 Methods to manage and manipulate FORTRAN variables.
394 Provides functionality for declaring, removing, and checking variables
395 in FORTRAN source code.
400 Initialize the Variables handler.
405 Keyword arguments for compatibility with parent class initialization.
412 Get the VarList for this scope.
417 VarList instance containing all variables in the current scope.
418 The list is lazily computed on first access.
422 >>> pft = PYFT('input.F90')
425 {'n': 'X', 't': 'REAL', ...}
428 if self.mainScope._varList
is None:
429 self.mainScope._varList =
VarList(self.mainScope)
438 Internal method to normalize scopeVarList
439 (list of tuples made of scope path, variable name, and optional other values)
441 return [(self.normalizeScope(scopePath), var.upper(), *other)
442 for (scopePath, var, *other)
in scopeVarList]
447 Internal method to suppress duplicates in scopeVarList
448 (list of tuples made of scope path, variable name, and optional other values)
454 if scopeVar
not in result:
455 result.append(scopeVar)
461 Move DIMENSION attribute from declaration statement to individual entities.
463 Finds all type declaration statements (T-decl-stmt) that have a DIMENSION
464 attribute and moves the array specification to each declared variable.
469 REAL, DIMENSION(D%NIJT,D%NKT) :: ZTLK, ZRT
470 INTEGER, PARAMETER, DIMENSION(2) :: IBUEXTRAIND=(/18, 30/)
473 REAL :: ZTLK(D%NIJT,D%NKT), ZRT(D%NIJT,D%NKT)
474 INTEGER, PARAMETER :: IBUEXTRAIND(2)=(/18, 30/)
478 - DIMENSION attribute must be in uppercase in the source code.
479 - Allocatable arrays (with ':') are not modified.
480 - Variables with existing array specifications are not modified.
484 >>> pft = PYFT('input.F90')
485 >>> pft.attachArraySpecToEntity()
486 >>> pft.write() # Writes transformed code
490 for decl
in self.findall(
'.//{*}T-decl-stmt'):
491 arraySpec = decl.find(
'./{*}attribute[{*}attribute-N="DIMENSION"]/{*}array-spec')
492 attrElem = decl.find(
'./{*}attribute[{*}attribute-N="DIMENSION"]/{*}array-spec/...')
494 if arraySpec
is not None and ':' not in alltext(arraySpec):
497 if decl.find(
'./{*}EN-decl-LT/{*}EN-decl/{*}array-spec')
is None and \
498 decl.find(
'./{*}EN-decl-LT/{*}EN-decl/{*}init-E')
is None:
500 for elem
in decl.findall(
'./{*}EN-decl-LT/{*}EN-decl'):
501 elem.append(copy.deepcopy(arraySpec))
503 self.removeFromList(attrElem, decl)
508 Check for missing IMPLICIT NONE statements in scopes.
510 Issues a logging warning if IMPLICIT NONE is not declared in a scope.
511 When mustRaise is True, logs an error and raises PYFTError instead.
515 mustRaise : bool, optional
516 If False (default), issue a warning and continue.
517 If True, issue an error and raise PYFTError.
521 >>> pft = PYFT('input.F90')
522 >>> pft.checkImplicitNone() # Issues warning if missing
523 >>> pft.checkImplicitNone(mustRaise=True) # Raises error if missing
525 for scope
in self.getScopes():
528 if (scope.path.count(
'/') == 0
or
529 (scope.path.count(
'/') >= 2
and
530 scope.path.split(
'/')[-2].startswith(
'interface:'))):
531 if scope.find(
'./{*}implicit-none-stmt')
is None:
532 message =
"The 'IMPLICIT NONE' statment is missing in file " + \
533 "'{file}' for {scopePath}.".format(file=scope.getFileName(),
534 scopePath=scope.path)
536 logging.error(message)
538 logging.warning(message)
543 Check for missing INTENT attributes on dummy arguments.
545 Issues a logging warning for each dummy argument without an INTENT attribute.
546 When mustRaise is True, logs an error and raises PYFTError.
550 mustRaise : bool, optional
551 If False (default), issue warnings and continue.
552 If True, issue errors and raise PYFTError.
556 >>> pft = PYFT('input.F90')
557 >>> pft.checkIntent() # Issues warning for missing INTENT
558 >>> pft.checkIntent(mustRaise=True) # Raises error
561 log = logging.error
if mustRaise
else logging.warning
563 if var[
'arg']
and var[
'i']
is None:
564 log((
"The dummy argument {} has no INTENT attribute, in " +
565 "file '{}'").format(var[
'n'], self.getFileName()))
567 if not ok
and mustRaise:
568 raise PYFTError(
"There are dummy arguments without INTENT attribute in " +
569 "file '{}'".format(self.getFileName()))
574 Check for missing ONLY clauses in USE statements.
576 Issues a logging warning for each USE statement not followed by an ONLY clause.
577 When mustRaise is True, logs an error and raises PYFTError.
581 mustRaise : bool, optional
582 If False (default), issue warnings and continue.
583 If True, issue errors and raise PYFTError.
587 >>> pft = PYFT('input.F90')
588 >>> pft.checkONLY() # Issues warning for missing ONLY
589 WARNING: USE MODULE is not followed by an ONLY clause...
590 >>> pft.checkONLY(mustRaise=True) # Raises error
593 log = logging.error
if mustRaise
else logging.warning
594 for useStmt
in self.findall(
'.//{*}use-stmt'):
595 module = useStmt.find(
'.//{*}module-N')
596 if module.tail
is None or module.tail.replace(
' ',
'').upper() !=
',ONLY:':
597 log(f
"USE {n2name(module.find('.//{*}N'))} is not followed by an ONLY clause " +
598 f
"in file'{self.getFileName()}'.")
599 if not ok
and mustRaise:
600 raise PYFTError(
"There are USE statements not followed by an ONLY clause " +
601 f
"file '{self.getFileName()}'")
606 @updateTree('signal')
609 Remove variables from declarations and argument lists.
613 varList : list of tuple
614 List of variables to remove. Each item is a list or tuple of two elements:
615 - First element: scope path where the variable is used (or declared).
616 This is a '/' separated path where each element has the form:
617 'module:<name>', 'sub:<name>', 'func:<name>', or 'type:<name>'.
618 - Second element: variable name (string).
620 Example: [('module:MOD/sub:SUB', 'X'), ('module:MOD/sub:SUB', 'Y')]
621 simplify : bool, optional
622 If True, also remove variables that become unused after the deletion
623 (e.g., kind selectors used only by the removed variable).
627 Remove variable X from subroutine SUB in module MOD:
628 >>> pft = PYFT('input.F90')
629 >>> pft.removeVar([('module:MOD/sub:SUB', 'X')])
631 Remove multiple variables with simplification:
632 >>> pft.removeVar([('module:MOD/sub:SUB', 'KIND_VAR')], simplify=True)
636 - Dummy arguments are removed from both declarations and argument lists.
637 - USE statement variables are removed from ONLY clauses.
638 - If all variables in a declaration statement are removed, the statement
639 itself is deleted (unless simplify=True, which may delete additional unused variables).
645 for scopePath, varName
in varList:
646 nb = scopePath.count(
'/')
647 sortedVarList[nb] = sortedVarList.get(nb, []) + [(scopePath, varName.upper())]
649 varToRemoveIfUnused = []
651 nbList = []
if len(sortedVarList.keys()) == 0
else \
652 range(max(sortedVarList.keys()) + 1)[::-1]
654 sortedVarList[nb] = sortedVarList.get(nb, [])
656 for scopePath
in list(set(scopePath
for scopePath, _
in sortedVarList[nb])):
658 scope = self.mainScope.getScopeNode(scopePath)
660 varNames = list(set(v
for (w, v)
in sortedVarList[nb]
if w == scopePath))
668 for node
in list(scope):
672 dummyList = node.find(
'{*}dummy-arg-LT')
673 if dummyList
is not None:
675 for arg
in dummyList.findall(
'.//{*}arg-N'):
676 name = n2name(arg.find(
'.//{*}N')).upper()
677 for varName
in [v
for v
in varNames
if v == name]:
679 scope.removeFromList(arg, dummyList)
682 if tag(node) == declStmt:
685 declList = node.find(
'./{*}EN-decl-LT')
686 for enDecl
in declList.findall(
'.//{*}EN-decl'):
687 name = n2name(enDecl.find(
'.//{*}N')).upper()
688 for varName
in [v
for v
in varNames
if v == name]:
691 varNames.remove(varName)
692 scope.removeFromList(enDecl, declList)
694 if len(list(declList.findall(
'./{*}EN-decl'))) == 0:
696 varToRemoveIfUnused.extend([[scopePath, n2name(nodeN)]
697 for nodeN
in node.findall(
'.//{*}N')])
700 if previous
is not None and node.tail
is not None:
701 if previous.tail
is None:
703 previous.tail += node.tail
705 scope.getParent(node).remove(node)
708 if tag(node) ==
'use-stmt':
710 useList = node.find(
'./{*}rename-LT')
711 if useList
is not None:
712 for rename
in useList.findall(
'.//{*}rename'):
713 name = n2name(rename.find(
'.//{*}N')).upper()
714 for varName
in [v
for v
in varNames
if v == name]:
715 varNames.remove(varName)
717 scope.removeFromList(rename, useList)
719 attribute = node.find(
'{*}module-N').tail
720 if attribute
is None:
722 attribute = attribute.replace(
' ',
'').replace(
'\n',
'')
723 attribute = attribute.replace(
'&',
'').upper()
724 useList = node.find(
'./{*}rename-LT')
725 if len(useList) == 0
and attribute[0] ==
',' and \
726 attribute[1:] ==
'ONLY:':
729 if previous
is not None and node.tail
is not None:
730 if previous.tail
is None:
732 previous.tail += node.tail
734 scope.getParent(node).remove(node)
735 scope.tree.signal(scope)
736 elif len(useList) == 0:
738 moduleName = scope.getSiblings(useList, before=
True,
740 previousTail = moduleName.tail
741 if previousTail
is not None:
742 moduleName.tail = previousTail.replace(
',',
'')
743 scope.getParent(useList).remove(useList)
745 if len(varNames) == 0:
752 if len(varNames) != 0:
753 newWhere =
'/'.join(scopePath.split(
'/')[:-1])
754 sortedVarList[nb - 1] = sortedVarList.get(nb - 1, []) + \
755 [(newWhere, varName)
for varName
in varNames]
757 if simplify
and len(varToRemoveIfUnused) > 0:
764 Add variables to declarations and argument lists.
768 varList : list of list/tuple
769 List of variable specifications to insert. Each specification is a list
771 - Scope path (str): path to the module, subroutine, function, or type
772 where the variable should be declared (e.g., 'module:MOD/sub:SUB').
773 - Variable name (str): name of the variable to add.
774 - Declaration statement (str): FORTRAN declaration (e.g., 'REAL, INTENT(IN) :: X').
775 - Position (int or None): position in dummy argument list for arguments,
776 None for local variables.
780 Add a local variable:
781 >>> pft = PYFT('input.F90')
782 >>> pft.addVar([('module:MOD/sub:SUB', 'LOCAL_VAR', 'INTEGER :: LOCAL_VAR', None)])
784 Add a dummy argument at position 0:
785 >>> pft.addVar([('module:MOD/sub:SUB', 'ARG', 'REAL, INTENT(IN) :: ARG', 0)])
787 Add multiple variables:
789 ... ('module:MOD/sub:SUB', 'X', 'REAL :: X', None),
790 ... ('module:MOD/sub:SUB', 'Y', 'INTEGER :: Y', None)
795 - If adding to an argument list, the declaration is automatically updated
796 with the INTENT attribute if specified.
797 - Declaration statements are inserted before the first executable statement.
801 for (scopePath, name, declStmt, pos)
in varList:
802 scope = self.getScopeNode(scopePath)
806 argN = createElem(
'arg-N')
807 nodeN = createElem(
'N')
808 nodeN.append(createElem(
'n', text=name))
811 argLst = scope.find(
'.//{*}dummy-arg-LT')
814 scope[0][0].tail =
'('
815 argLst = createElem(
'dummy-arg-LT', tail=
')')
816 scope[0].insert(1, argLst)
817 scope.insertInList(pos, argN, argLst)
822 if declStmt
is not None and declStmt !=
'':
826 if scopePath.split(
'/')[-1].split(
':')[0] ==
'type':
829 ds = createExpr(declStmt)[0]
830 previousTail =
'\n' + declStmt[:re.search(
r'\S', declStmt).start()]
836 ds.tail = scope[-2].tail
837 scope[-2].tail = previousTail
843 ds = createExpr(declStmt)[0]
844 previousTail =
'\n' + declStmt[:re.search(
r'\S', declStmt).start()]
847 declLst = [node
for node
in scope
if tag(node) == declStmtTag]
848 if len(declLst) != 0:
853 index = list(scope).index(decl)
854 if name
in [n2name(nodeN)
for nodeN
in decl.findall(
'.//{*}N')]:
859 stmtLst = [node
for node
in scope
if isExecutable(node)]
860 if len(stmtLst) == 0:
863 index = len(scope) - 1
866 index = list(scope).index(stmtLst[0])
870 ds.tail = scope[index - 1].tail
871 scope[index - 1].tail = previousTail
872 scope.insert(index, ds)
877 @updateTree('signal')
880 Add USE statements for module variables.
884 moduleVarList : list of list/tuple
885 List of module variable specifications. Each specification is a list
887 - Scope path (str): path to the location where USE should be added
888 (e.g., 'module:MOD/sub:SUB').
889 - Module name (str): name of the module to USE.
890 - Variable name(s) (str, list, or None):
891 - str: single variable name to import.
892 - list: list of variable names to import.
893 - None: add USE without ONLY clause (import all).
897 Import single variable Y from MODD_XX into subroutine FOO:
898 >>> pft = PYFT('input.F90')
899 >>> pft.addModuleVar([('sub:FOO', 'MODD_XX', 'Y')])
900 ! Adds: USE MODD_XX, ONLY: Y
902 Import multiple variables:
903 >>> pft.addModuleVar([('sub:FOO', 'MODD_XX', ['X', 'Y', 'Z'])])
904 ! Adds: USE MODD_XX, ONLY: X, Y, Z
906 Add USE without ONLY (import all):
907 >>> pft.addModuleVar([('sub:FOO', 'MODD_XX', None)])
910 Import into a module:
911 >>> pft.addModuleVar([('module:MOD', 'OTHER_MOD', 'VAR')])
915 - Existing USE statements for the same module are updated to include new variables.
916 - Duplicate imports are avoided (variables already imported are not re-added).
920 for (scopePath, moduleName, varName)
in moduleVarList:
923 elif not isinstance(varName, list):
925 scope = self.getScopeNode(scopePath)
928 useLst = [node
for node
in scope
if tag(node) ==
'use-stmt']
933 usName = n2name(us.find(
'.//{*}module-N//{*}N'))
934 usVar = [n2name(v.find(
'.//{*}N')).upper()
for v
in us.findall(
'.//{*}use-N')]
935 if len(varName) == 0
and len(usVar) == 0
and usName.upper() == moduleName.upper():
938 elif len(varName) > 0
and len(usVar) > 0
and usName.upper() == moduleName.upper():
942 varName = [var
for var
in varName
if var.upper()
not in usVar]
943 if len(varName) == 0:
949 stmt = f
'USE {moduleName}'
951 stmt +=
', ONLY:{}'.format(
', '.join(varName))
952 us = createExpr(stmt)[0]
957 index = list(scope).index(useLst[-1]) + 1
962 us.tail = scope[index - 1].tail
963 scope[index - 1].tail =
'\n'
964 scope.insert(index, us)
965 scope.tree.signal(scope)
970 Display unused variables on stdout.
972 Searches through all scopes and displays variables that are declared
973 but never used in the code.
977 >>> pft = PYFT('input.F90')
978 >>> pft.showUnusedVar()
979 Some variables declared in /module:MOD/sub:SUB are unused:
983 scopes = self.getScopes(excludeKinds=[
'type'])
984 varUsed = self.
isVarUsed([(scope.path, v[
'n'])
987 if v[
'scopePath'] == scope.path])
989 varList = [k[1].upper()
for (k, v)
in varUsed.items()
if (
not v)
and k[0] == scope.path]
990 if len(varList) != 0:
991 print(f
'Some variables declared in {scope.path} are unused:')
992 print(
' - ' + (
'\n - '.join(varList)))
997 Check for unused local variables in scopes.
999 Issues a logging warning for each local variable that is declared but never used.
1000 When mustRaise is True, logs an error and raises PYFTError.
1004 mustRaise : bool, optional
1005 If False (default), issue warnings and continue.
1006 If True, issue errors and raise PYFTError.
1007 excludeList : list of str, optional
1008 List of variable names to exclude from the check.
1009 These variables will not trigger warnings even if unused.
1013 >>> pft = PYFT('input.F90')
1014 >>> pft.checkUnusedLocalVar() # Issues warnings
1015 WARNING: The LOCAL_VAR variable is not used...
1016 >>> pft.checkUnusedLocalVar(excludeList=['TEMP']) # Exclude TEMP
1017 >>> pft.checkUnusedLocalVar(mustRaise=True) # Raises error
1020 if excludeList
is None:
1023 excludeList = [v.upper()
for v
in excludeList]
1024 scopes = self.getScopes(excludeKinds=[
'type'])
1026 varUsed = self.
isVarUsed([(scope.path, v[
'n'])
1029 if (v[
'n'].upper()
not in excludeList
and
1031 v[
'scopePath'].split(
'/')[-1].split(
':')[0] !=
'module' and
1032 v[
'scopePath'] == scope.path)])
1033 for scope
in scopes:
1034 for var
in [k[1].upper()
for (k, v)
in varUsed.items()
1035 if (
not v)
and k[0] == scope.path]:
1036 message = f
"The {var} variable is not used in file " + \
1037 f
"'{scope.getFileName()}' for {scope.path}."
1039 logging.error(message)
1041 logging.warning(message)
1046 Remove unused local variables from declarations.
1048 Removes variables that are declared but never used in the code.
1049 Dummy arguments and module variables are not removed.
1053 excludeList : list of str, optional
1054 List of variable names to exclude from removal.
1055 These variables will be kept even if unused.
1056 simplify : bool, optional
1057 If True, also remove variables that become unused after removal
1058 (e.g., kind selectors).
1062 >>> pft = PYFT('input.F90')
1063 >>> pft.removeUnusedLocalVar() # Remove all unused locals
1065 Remove unused locals except TEMP and COUNTER:
1066 >>> pft.removeUnusedLocalVar(excludeList=['TEMP', 'COUNTER'])
1068 With simplification (remove cascading unused vars):
1069 >>> pft.removeUnusedLocalVar(simplify=True)
1073 - Only local variables (declared in SUBROUTINE/FUNCTION scope) are removed.
1074 - Dummy arguments (subroutine parameters) are preserved.
1075 - Variables imported via USE statements are preserved.
1077 if excludeList
is None:
1080 excludeList = [item.upper()
for item
in excludeList]
1082 allVar = [(scope.path, v[
'n'])
1083 for scope
in self.getScopes(excludeKinds=[
'type'])
1084 for v
in scope.varList
1085 if v[
'n'].upper()
not in excludeList
and v[
'scopePath'] == scope.path]
1086 self.
removeVarIfUnused(allVar, excludeDummy=
True, excludeModule=
True, simplify=simplify)
1091 Replace implicit array bounds with explicit bounds from declarations.
1093 Transforms array slice notation (e.g., A(:)) into explicit bounds
1094 based on the array's declaration.
1098 node : xml element, optional
1099 Specific XML node to transform. If None (default), transforms
1100 all implicit bounds in all scopes.
1104 Given declaration: REAL, DIMENSION(1:10) :: A, B
1105 And usage: A(:) = B(:)
1107 After transformation:
1112 - Only handles 1D array slices (A(:) notation).
1113 - Does not modify allocatable arrays (where ':' is part of declaration).
1114 - Does not modify character type arrays.
1117 nodes = [(scope, scope)
for scope
in self.getScopes()]
1119 nodes = [(self, node)]
1121 for (scope, childNode)
in nodes:
1122 for parent4
in [parent4
for parent4
1123 in childNode.findall(
'.//{*}section-subscript/../../../..')
1124 if parent4.find(
'./{*}R-LT/{*}component-R')
is None]:
1126 for parent
in parent4.findall(
'.//{*}section-subscript/..'):
1127 for sub
in parent.findall(
'.//{*}section-subscript'):
1128 lowerUsed = sub.find(
'./{*}lower-bound')
1129 upperUsed = sub.find(
'./{*}upper-bound')
1134 if lowerUsed
is None or \
1135 (lowerUsed.tail
is not None and ':' in lowerUsed.tail):
1136 if lowerUsed
is None or upperUsed
is None:
1138 varDesc = scope.varList.findVar(n2name(parent4.find(
'.//{*}N')))
1139 if varDesc
is not None and varDesc[
't']
is not None and \
1140 'CHAR' not in varDesc[
't']:
1141 lowerDecl, upperDecl = varDesc[
'as'][list(parent).index(sub)]
1142 if lowerDecl
is None:
1146 lowerBound = lowerDecl
if lowerUsed
is None \
1147 else alltext(lowerUsed)
1148 upperBound = upperDecl
if upperUsed
is None \
1149 else alltext(upperUsed)
1150 if upperBound
is not None:
1151 lowerXml, upperXml = createArrayBounds(lowerBound,
1157 if tag(nnn)
in (
'lower-bound',
'upper-bound'):
1161 "tag is {}".format(tag(nnn)))
1163 sub.extend([lowerXml, upperXml])
1169 Add explicit array parentheses to array variables.
1171 Transforms array variable references to include explicit slice notation.
1172 For example, A becomes A(:) when A is declared as an array.
1176 Given declaration: REAL, DIMENSION(10) :: A, B, C
1177 And usage: A = B + C
1179 After transformation:
1184 - Only modifies known arrays (declared with dimensions).
1185 - Excludes arrays in ALLOCATED, ASSOCIATED, and PRESENT intrinsic calls.
1186 - Does not modify pointer/allocatable arrays in call statements
1187 (to maintain interface compatibility).
1190 for scope
in self.getScopes():
1191 for node
in scope.iter():
1211 nodeToTransform =
None
1212 if tag(node)
in (
'allocate-stmt',
'deallocate-stmt',
'pointer-a-stmt',
1213 'T-decl-stmt',
'associate-stmt',
'function-stmt',
1214 'subroutine-stmt',
'interface-stmt',
'action-stmt',
1218 elif tag(node)
in (
'if-stmt',
'where-stmt',
'forall-stmt'):
1220 part = {
'if-stmt':
'condition-E',
'where-stmt':
'mask-E',
1221 'forall-stmt':
'forall-triplet-spec-LT'}[tag(node)]
1222 nodeToTransform = node.find(
'./{*}' + part)
1223 elif tag(node).endswith(
'-stmt'):
1224 nodeToTransform = node
1225 if nodeToTransform
is not None:
1226 scope.addArrayParenthesesInNode(nodeToTransform)
1231 Add explicit array parentheses to arrays within a specific XML node.
1236 XML node in which to add array parentheses.
1237 Only processes named-E elements within this node.
1241 addArrayParentheses : Add parentheses in all scopes.
1245 >>> pft = PYFT('input.F90')
1246 >>> node = pft.find('.//{*}a-stmt')
1247 >>> pft.addArrayParenthesesInNode(node)
1250 for namedE
in node.findall(
'.//{*}named-E'):
1251 if not namedE.find(
'./{*}R-LT'):
1252 if not self.isNodeInProcedure(namedE, (
'ALLOCATED',
'ASSOCIATED',
'PRESENT')):
1255 nodeN = namedE.find(
'./{*}N')
1256 var = self.
varList.findVar(n2name(nodeN))
1257 if var
is not None and var[
'as']
is not None and len(var[
'as']) > 0
and \
1258 not ((var[
'pointer']
or var[
'allocatable'])
and self.isNodeInCall(namedE)):
1264 nodeRLT = createElem(
'R-LT')
1265 namedE.insert(list(namedE).index(nodeN) + 1, nodeRLT)
1266 arrayR = createElem(
'array-R', text=
'(')
1267 nodeRLT.append(arrayR)
1268 sectionSubscriptLT = createElem(
'section-subscript-LT', tail=
')')
1269 arrayR.append(sectionSubscriptLT)
1271 sectionSubscript = createElem(
'section-subscript', text=
':', tail=
', ')
1272 sectionSubscriptLT.append(sectionSubscript)
1273 sectionSubscript.tail =
None
1278 Remove array parentheses if no index selection is needed.
1280 When an array has full slice notation (e.g., A(:,:)) that matches
1281 the entire array, removes the parentheses.
1286 XML node in which to remove unnecessary parentheses.
1290 Before: A(:,:) (when A is declared as 2D with full bounds)
1295 - Only removes parentheses when all dimensions use full slice (:) notation.
1296 - Preserves parentheses if any dimension has explicit index selection.
1297 - Excludes arrays in ALLOCATED, ASSOCIATED, and PRESENT intrinsic calls.
1300 for namedE
in node.findall(
'.//{*}named-E'):
1301 if namedE.find(
'./{*}R-LT'):
1302 if not self.isNodeInProcedure(namedE, (
'ALLOCATED',
'ASSOCIATED',
'PRESENT')):
1305 nodeN = namedE.find(
'./{*}N')
1306 var = self.
varList.findVar(n2name(nodeN))
1307 if var
is not None and var[
'as']
is not None and len(var[
'as']) > 0
and \
1308 not ((var[
'pointer']
or var[
'allocatable'])
and self.isNodeInCall(namedE)):
1309 arrayR = namedE.findall(
'./{*}R-LT')
1311 sectionSubscriptLT = arr.findall(
'.//{*}section-subscript-LT')
1312 for ss
in sectionSubscriptLT:
1313 lowerBound = ss.findall(
'.//{*}lower-bound')
1314 if len(lowerBound) == 0:
1316 par = self.getParent(ss, level=2)
1317 parOfpar = self.getParent(par)
1318 parOfpar.remove(par)
1324 Transform automatic arrays using customizable templates.
1326 Modifies automatic array declarations in subroutines and functions by
1327 applying templates for declaration, initialization, and cleanup.
1331 declTemplate : str, optional
1332 Template for the array declaration. If None, declaration is unchanged.
1333 startTemplate : str, optional
1334 Template for code to insert as the first executable statement
1336 endTemplate : str, optional
1337 Template for code to insert as the last executable statement
1338 (e.g., deallocation).
1343 Number of arrays modified.
1347 Each template can use the following placeholders (case-sensitive):
1349 ============ ==================================================
1350 Placeholder Description (for declaration "A(I, I:J, 0:I)")
1351 ============ ==================================================
1352 {name} Variable name (e.g., "A")
1353 {type} Type specification (e.g., "REAL")
1354 {doubledotshape} Colon-separated dimensions (e.g., ":, :, :")
1355 {shape} Bounds with indices (e.g., "I, I:J, 0:I")
1356 {lowUpList} Flattened bounds (e.g., "1, I, I, J, 0, I")
1357 ============ ==================================================
1361 Transform automatic arrays to allocatables:
1363 >>> pft = PYFT('input.F90')
1364 >>> pft.modifyAutomaticArrays(
1365 ... declTemplate="{type}, DIMENSION({doubledotshape}), ALLOCATABLE :: {name}",
1366 ... startTemplate="ALLOCATE({name}({shape}))",
1367 ... endTemplate="DEALLOCATE({name})"
1371 REAL, DIMENSION(I, I:J, 0:I) :: A
1375 REAL, DIMENSION(:,:,:), ALLOCATABLE :: A
1376 ALLOCATE(A(I, I:J, 0:I))
1382 - Only processes automatic arrays (stack-allocated based on parameters).
1383 - Excludes dummy arguments, allocatable, pointer, and function result arrays.
1384 - Arrays with initial values are not processed.
1385 - Variables are processed in dependency order to handle interdependencies.
1387 templates = {
'decl': declTemplate
if declTemplate
is not None else '',
1388 'start': startTemplate
if startTemplate
is not None else '',
1389 'end': endTemplate
if endTemplate
is not None else ''}
1392 for scope
in [scope
for scope
in self.getScopes()
1393 if scope.path.split(
'/')[-1].split(
':')[0]
in (
'sub',
'func')]:
1396 varListToTransform = []
1397 for var
in [var
for var
in scope.varList
1398 if var[
'as']
is not None and
1399 len(var[
'as']) > 0
and
1400 'CHARACTER' not in var[
't']
and
1401 not (var[
'arg']
or var[
'allocatable']
or
1402 var[
'pointer']
or var[
'result'])]:
1405 if var[
'init']
is None:
1407 varListToTransform.append(var)
1411 orderedVarListToTransform = []
1412 while len(varListToTransform) > 0:
1414 for var
in varListToTransform[:]:
1416 listN = [x
for dim
in var[
'asx']
for x
in dim
if x
is not None]
1417 listN = [n2name(nodeN).upper()
for asx
in listN
1418 for nodeN
in asx.findall(
'.//{*}N/{*}n/..')]
1419 if len(set(listN).intersection([v[
'n'].upper()
1420 for v
in varListToTransform])) == 0:
1422 varListToTransform.remove(var)
1423 orderedVarListToTransform.append(var)
1426 raise PYFTError(
'It seems that there is a circular reference in ' +
1427 'the declaration statements')
1429 for var
in orderedVarListToTransform[::-1]:
1432 templ = copy.deepcopy(templates)
1433 for templPart
in templ:
1434 if '{doubledotshape}' in templ[templPart]:
1435 templ[templPart] = templ[templPart].replace(
1436 '{doubledotshape}',
','.join([
':'] * len(var[
'as'])))
1437 if '{shape}' in templ[templPart]:
1439 for i
in range(len(var[
'as'])):
1440 if var[
'as'][i][0]
is None:
1441 result.append(var[
'as'][i][1])
1443 result.append(var[
'as'][i][0] +
':' + var[
'as'][i][1])
1444 templ[templPart] = templ[templPart].replace(
'{shape}',
', '.join(result))
1445 if '{name}' in templ[templPart]:
1446 templ[templPart] = templ[templPart].replace(
'{name}', var[
'n'])
1447 if '{type}' in templ[templPart]:
1448 templ[templPart] = templ[templPart].replace(
'{type}', var[
't'])
1449 if '{lowUpList}' in templ[templPart]:
1451 for i
in range(len(var[
'as'])):
1452 if var[
'as'][i][0]
is None:
1453 result.extend([1, var[
'as'][i][1]])
1455 result.extend([var[
'as'][i][0], var[
'as'][i][1]])
1456 templ[templPart] = templ[templPart].replace(
1457 '{lowUpList}',
', '.join([str(r)
for r
in result]))
1460 separator =
"!ABCDEFGHIJKLMNOPQRSTUVWabcdefghijklmnopqrstuvwxyz0123456789"
1462 for node
in createExpr(templ[
'decl'] +
'\n' + separator +
'\n' +
1463 templ[
'start'] +
'\n' + separator +
'\n' +
1465 templPart = list(templ.keys())[part]
1466 if not isinstance(templ[templPart], list):
1467 templ[templPart] = []
1468 if tag(node) ==
'C' and node.text == separator:
1471 templ[templPart].append(node)
1477 for decl
in scope.findall(
'./{*}T-decl-stmt'):
1478 index = list(scope).index(decl)
1479 if var[
'n']
in [n2name(nodeN)
for nodeN
in decl.findall(
'.//{*}N')]:
1481 scope.removeVar([(scope.path, var[
'n'])], simplify=
False)
1482 for nnn
in templ[
'decl'][::-1]:
1483 scope.insert(index, nnn)
1486 for nnn
in templ[
'start'][::-1]:
1487 scope.insertStatement(nnn,
True)
1488 for nnn
in templ[
'end'][::-1]:
1489 scope.insertStatement(nnn,
False)
1496 :param varSpec: a variable description, same form as the items return by self.varList
1497 :param implicitDeclaration: True if the variable may contain implicit declaration
1498 (e.g. outside of PHYEX)
1499 :return: the associated declarative statement
1501 if varSpec[
'use']
is not False:
1502 stmt = f
"USE {varSpec['use']}, ONLY: {varSpec['n']}"
1506 stmt +=
', DIMENSION('
1508 for el
in varSpec[
'as']:
1509 if el[0]
is None and el[1]
is None:
1514 dl.append(el[0] +
':' + el[1])
1515 if (varSpec[
'allocatable']
and not all(d ==
':' for d
in dl))
or \
1516 (any(d ==
':' for d
in dl)
and not varSpec[
'allocatable']):
1517 if not implicitDeclaration:
1518 raise PYFTError(
'Missing dim are mandatory and allowed ' +
1519 'only for allocatable arrays')
1520 stmt +=
', '.join(dl) +
')'
1521 if varSpec[
'allocatable']:
1522 stmt +=
", ALLOCATABLE"
1523 if varSpec[
'parameter']:
1524 stmt +=
", PARAMETER"
1525 if varSpec[
'i']
is not None:
1526 stmt +=
", INTENT(" + varSpec[
'i'] +
")"
1527 if varSpec[
'opt']
is True:
1528 stmt +=
", OPTIONAL"
1529 stmt +=
" :: " + varSpec[
'n']
1530 if varSpec[
'init']
is not None:
1531 stmt +=
"=" + varSpec[
'init']
1537 Find bounds and loop variable for a given array index
1538 :param arr: array node (named-E node with a array-R child)
1539 :param index: index of the rank of the array
1540 :param loopVar: None to create new variable for each added DO loop
1541 or a function that return the name of the variable to use for the
1543 This function returns a string (name of the variable), or True to create
1544 a new variable, or False to not transform this statement
1545 The functions takes as arguments:
1546 - lower and upper bounds as defined in the declaration statement
1547 - lower and upper bounds as given in the statement
1550 :return: the tuple (loopName, lowerBound, upperBound) where:
1551 loopName is the name of the variable to use for the loop
1552 lower and upper bounds are the bounds to use for the DO loop
1555 - False to discard this index
1556 - True to create a new variable to loop with
1558 name = n2name(arr.find(
'./{*}N'))
1559 ss = arr.findall(
'./{*}R-LT/{*}array-R/{*}section-subscript-LT/{*}section-subscript')[index]
1562 if ':' in alltext(ss):
1564 lowerUsed = ss.find(
'./{*}lower-bound')
1565 upperUsed = ss.find(
'./{*}upper-bound')
1566 varDesc = self.
varList.findVar(name, array=
True)
1567 if varDesc
is not None:
1568 lowerDecl, upperDecl = varDesc[
'as'][index]
1569 if lowerDecl
is None:
1572 lowerDecl, upperDecl =
None,
None
1578 varName = lowerDecl
is not None and upperDecl
is not None
1580 varName = loopVar(lowerDecl, upperDecl,
1581 None if lowerUsed
is None else alltext(lowerUsed),
1582 None if upperUsed
is None else alltext(upperUsed), name, index)
1584 lowerDecl
if lowerUsed
is None else alltext(lowerUsed),
1585 upperDecl
if upperUsed
is None else alltext(upperUsed))
1591 Transform a array-R into a parens-R node by replacing slices by variables
1592 In 'A(:)', the ':' is in a array-R node whereas in 'A(JL)', 'JL' is in a parens-R node.
1593 Both the array-R and the parens-R nodes are inside a R-LT node
1594 :param namedE: a named-E node
1595 :param table: dictionnary returned by the decode function
1596 :param varList: None or a VarList object in which varaibles are searched for
1619 nodeRLT = namedE.find(
'./{*}R-LT')
1620 arrayR = nodeRLT.find(
'./{*}array-R')
1621 if arrayR
is not None:
1622 index = list(nodeRLT).index(arrayR)
1623 parensR = createElem(
'parens-R', text=
'(', tail=
')')
1624 elementLT = createElem(
'element-LT')
1625 parensR.append(elementLT)
1627 for ss
in nodeRLT[index].findall(
'./{*}section-subscript-LT/{*}section-subscript'):
1628 element = createElem(
'element', tail=
', ')
1629 elementLT.append(element)
1630 if ':' in alltext(ss):
1632 varName = list(table.keys())[ivar]
1633 lower = ss.find(
'./{*}lower-bound')
1634 upper = ss.find(
'./{*}upper-bound')
1635 if lower
is not None:
1636 lower = alltext(lower)
1637 if upper
is not None:
1638 upper = alltext(upper)
1639 if lower
is not None and ss.text
is not None and ':' in ss.text:
1643 if lower
is None and upper
is None:
1647 element.append(createExprPart(varName))
1654 lower = self.
varList.findVar(n2name(namedE.find(
'{*}N')),
1655 array=
True)[
'as'][ivar][0]
1660 upper = self.
varList.findVar(n2name(namedE.find(
'{*}N')),
1661 array=
True)[
'as'][ivar][1]
1667 newlower = simplifyExpr(lower, add=varName, sub=table[varName][0])
1668 newupper = simplifyExpr(upper, add=varName, sub=table[varName][1])
1669 if newlower != newupper:
1670 raise PYFTError((
"Don't know how to do with an array declared with " +
1671 "'{la}:{ua}' and a loop from '{ll}' to '{ul}'"
1672 ).format(la=lower, ua=upper,
1673 ll=table[varName][0],
1674 ul=table[varName][1]))
1675 element.append(createExprPart(newlower))
1677 element.append(ss.find(
'./{*}lower-bound'))
1679 nodeRLT.remove(nodeRLT[index])
1680 nodeRLT.insert(index, parensR)
1685 Find bounds and loop variable given an array
1686 :param arr: array node (named-E node with a array-R child)
1687 :param loopVar: None to create new variable for each added DO loop
1688 or a function that return the name of the variable to use for the loop
1690 This function returns a string (name of the variable), or True to create
1691 a new variable, or False to not transform this statement
1692 The functions takes as arguments:
1693 - lower and upper bounds as defined in the declaration statement
1694 - lower and upper bounds as given in the statement
1697 :param extraVarList: None or list of variables (such as those contained in a VarList object)
1698 defined but not yet available in the self.varList object.
1699 :return: the tuple (table, newVar) where:
1700 table is a dictionnary: keys are loop variable names
1701 values are tuples with lower and upper bounds
1702 newVar is a list of loop variables not found in varList. This list has the same
1703 format as the varList list.
1705 In case the loop variable cannot be defined, the function returns (None, [])
1708 name = n2name(arr.find(
'./{*}N'))
1710 extraVarList = extraVarList
if extraVarList
is not None else []
1713 for iss, ss
in enumerate(arr.findall(
'./{*}R-LT/{*}array-R/{*}section-subscript-LT/' +
1714 '{*}section-subscript')):
1717 if ':' in alltext(ss):
1722 if varName
is not False and varName
in table:
1723 raise PYFTError((
"The variable {var} must be used for the rank #{i1} whereas " +
1724 "it is already used for rank #{i2} (for array {name})."
1725 ).format(var=varName, i1=str(iss),
1726 i2=str(list(table.keys()).index(varName)),
1737 varName =
'J' + str(j)
1738 var = self.
varList.findVar(varName, extraVarList=extraVarList + varNew)
1739 if (var
is None or var.get(
'new',
False))
and varName
not in table:
1741 varDesc = {
'as': [],
'asx': [],
'n': varName,
'i':
None,
1742 't':
'INTEGER',
'arg':
False,
'use':
False,
'opt':
False,
1743 'scopePath': self.
path}
1744 if varDesc
not in varNew:
1745 varNew.append(varDesc)
1747 elif (varName
is not False and
1748 self.
varList.findVar(varName, array=
False, exactScope=
True)
is None):
1750 varDesc = {
'as': [],
'asx': [],
'n': varName,
'i':
None,
1751 't':
'INTEGER',
'arg':
False,
'use':
False,
'opt':
False,
1752 'scopePath': self.
path}
1753 varNew.append(varDesc)
1756 table[varName] = (lower, upper)
1758 return (
None, [])
if False in table
else (table, varNew)
1764 :param oldName: old name of the variable
1765 :param newName: new name of the variable
1767 for node
in self.findall(
'.//{*}N'):
1768 if n2name(node).upper() == oldName.upper():
1770 for nnn
in node.findall(
'./{*}n')[1:]:
1773 node.find(
'./{*}n').text = newName
1778 :param varList: list of variables to remove if unused. Each item is a list or tuple of two
1780 The first one describes where the variable is used, the second one is
1781 the name of the variable. The first element is a '/'-separated path with
1782 each element having the form 'module:<name of the module>',
1783 'sub:<name of the subroutine>' or 'func:<name of the function>'
1784 :param excludeDummy: if True, dummy arguments are always kept untouched
1785 :param excludeModule: if True, module variables are always kept untouched
1786 :param simplify: try to simplify code (if we delete a declaration statement that used a
1787 variable as kind selector, and if this variable is not used else where,
1789 :return: the varList without the unremovable variables
1790 If possible, remove the variable from declaration, and from the argument list if needed
1794 varList = [v
for v
in varList
if v[0].split(
'/')[-1].split(
':')[0] !=
'module']
1796 varUsed = self.
isVarUsed(varList, dummyAreAlwaysUsed=excludeDummy)
1797 varListToRemove = []
1798 for scopePath, varName
in varList:
1799 assert scopePath.split(
'/')[-1].split(
':')[0] !=
'type', \
1800 "The removeVarIfUnused cannot be used with type members"
1801 if not varUsed[(scopePath, varName)]:
1802 varListToRemove.append([scopePath, varName])
1803 self.
removeVar(varListToRemove, simplify=simplify)
1804 return varListToRemove
1807 def isVarUsed(self, varList, exactScope=False, dummyAreAlwaysUsed=False):
1809 :param varList: list of variables to test. Each item is a list or tuple of two elements.
1810 The first one describes where the variable is declared, the second one is
1811 the name of the variable. The first element is a '/'-separated path with
1812 each element having the form 'module:<name of the module>',
1813 'sub:<name of the subroutine>' or 'func:<name of the function>'
1814 :param exactScope: True to search strictly in scope
1815 :param dummyAreAlwaysUsed: Returns True if variable is a dummy argument
1816 :return: a dict whose keys are the elements of varList, and values are True when the
1817 variable is used, False otherwise
1819 If exactScope is True, the function will search for variable usage
1820 only in this scope. But this feature has a limited interest.
1822 If exactScope is False:
1823 - if scopePath is a subroutine/function in a contains section,
1824 and if the variable is not declared in this scope, usages are
1825 searched in the module/subroutine/function upper that declared
1826 the variable and in all subroutines/functions in the contains section
1827 - if scopePath is a module/subroutine/function that has a
1828 contains sections, usages are searched in all subroutines/functions
1829 in the contains section
1831 To know if a variable can be removed, you must use exactScope=False
1835 allScopes = {scope.path: scope
for scope
in self.mainScope.getScopes()}
1839 locsVar = {(scopePath, varName): [scopePath]
1840 for scopePath, varName
in varList}
1843 for scopePath, varName
in varList:
1846 var = allScopes[scopePath].varList.findVar(varName)
1847 path = scopePath.split(
'/')[0]
if var
is None else var[
'scopePath']
1852 for scPath, sc
in allScopes.items():
1853 if scPath.startswith(path +
'/')
and \
1854 scPath.split(
'/')[-1].split(
':')[0] !=
'type':
1856 if sc.varList.findVar(varName, exactScope=
True)
is None:
1858 testScopes.append(scPath)
1859 locsVar[(scopePath, varName)] = testScopes
1863 for scopePath
in list(set(item
for sublist
in locsVar.values()
for item
in sublist)):
1864 usedVar[scopePath] = []
1866 for node
in allScopes[scopePath]:
1869 if not tag(node) ==
'use-stmt':
1870 if tag(node) ==
'T-decl-stmt':
1874 nodesN = node.findall(
'.//{*}_T-spec_//{*}N') + \
1875 node.findall(
'.//{*}shape-spec//{*}N')
1877 nodesN = node.findall(
'.//{*}N')
1880 for nodeN
in nodesN:
1881 if dummyAreAlwaysUsed:
1885 usedVar[scopePath].append(n2name(nodeN).upper())
1887 parPar = allScopes[scopePath].getParent(nodeN, 2)
1890 if parPar
is None or not tag(parPar) ==
'dummy-arg-LT':
1891 usedVar[scopePath].append(n2name(nodeN).upper())
1894 for scopePath, varName
in varList:
1895 assert scopePath.split(
'/')[-1].split(
':')[0] !=
'type', \
1896 'We cannot check type component usage'
1897 result[(scopePath, varName)] = any(varName.upper()
in usedVar[scopePath]
1898 for scopePath
in locsVar[(scopePath, varName)])
1904 @updateTree('signal')
1905 def addONLY(self, parserOptions=None, wrapH=False):
1907 Adds missing ONLY clause to USE statements
1908 :param parserOptions, wrapH: see the PYFT class
1910 for useStmt
in self.findall(
'.//{*}use-stmt'):
1911 module = useStmt.find(
'.//{*}module-N')
1912 if module.tail
is None or module.tail.replace(
' ',
'').upper() !=
',ONLY:':
1914 modulename = n2name(module.find(
'.//{*}N')).upper()
1915 modulescope =
'module:' + modulename
1916 modulefile = self.tree.scopeToFiles(modulescope)
1917 if len(modulefile) != 1:
1918 raise PYFTError(f
'No or several files define the {modulename} module')
1923 if self.getFileName() == os.path.normpath(modulefile[0]):
1925 xml = self.mainScope
1929 modulefile[0], parserOptions, wrapH, tree=self.tree,
1930 clsPYFT=self._mainScope.__class__)
1934 symbols.extend(v[
'n']
for v
in xml.varList.restrict(modulescope,
True))
1936 for scope
in xml.getScopeNode(modulescope,
1937 excludeContains=
False).getScopes(
1939 excludeContains=
False,
1940 includeItself=
False):
1941 if scope.path.split(
'/')[1].split(
':')[0] ==
'interface':
1942 if scope.path.split(
'/')[1].split(
':')[1] !=
'--UNKNOWN--':
1944 symbols.append(scope.path.split(
'/')[1].split(
':')[1])
1945 if len(scope.path.split(
'/')) == 3:
1947 symbols.append(scope.path.split(
'/')[2].split(
':')[1])
1949 symbols.append(scope.path.split(
'/')[1].split(
':')[1])
1950 symbols = sorted(set(symbols))
1957 module.tail =
', ONLY: '
1958 renameLT = createElem(
'rename-LT')
1959 useStmt.append(renameLT)
1961 parent = self.getScopePath(useStmt)
1962 isVarUsed = self.
isVarUsed([(parent, symbol.upper())
for symbol
in symbols])
1963 for symbol
in symbols:
1964 if isVarUsed[(parent, symbol.upper())]:
1965 useN = createElem(
'use-N', childs=createElem(
'N',
1966 childs=createElem(
'n', text=symbol)))
1967 rename = createElem(
'rename', tail=
', ', childs=useN)
1968 renameLT.append(rename)
1971 previous = self.getSiblings(useStmt, before=
True, after=
False)
1972 previous =
None if len(previous) == 0
else previous[-1]
1973 if previous
is not None and useStmt.tail
is not None:
1974 if previous.tail
is None:
1976 previous.tail += useStmt.tail
1977 self.getParent(useStmt).remove(useStmt)
1978 self.tree.signal(self)
1988 def addArgInTree(self, varName, declStmt, pos, stopScopes, moduleVarList=None,
1990 parserOptions=None, wrapH=False):
1992 Adds an argument to the routine and propagates it upward until we encounter a scope
1993 where the variable exists or a scope in stopScopes
1994 :param varName: variable name
1995 :param declStmt: declarative statment (will be used by addVar)
1996 :param pos: position of the variable in the list of dummy argument
1997 :param stopScopes: list of scopes to reach
1998 :param moduleVarList: list of module variable specification to insert in the xml code
1999 a module variable specification is a list of two elements:
2001 - variable name or or list of variable names
2002 or None to add a USE statement without the ONLY attribute
2003 use moduleVarList to not add module variables
2004 :param otherNames: None or list of other variable names that can be used
2005 These variables are used first
2006 :param parserOptions, wrapH: see the PYFT class
2008 Argument is inserted only on paths leading to one of scopes listed in stopScopes
2010 def insertInArgList(varName, varNameToUse, pos, callFuncStmt):
2012 Insert varName in the list of arguments to the subroutine or function call
2013 :param varName: name of the dummy argument
2014 :param varNameToUse: name of the variable
2015 :param pos: inclusion position
2016 :param callFuncStmt: call statement or function call
2018 argList = callFuncStmt.find(
'./{*}R-LT/{*}parens-R/{*}element-LT')
2019 if argList
is not None:
2020 container = createElem(
'element')
2022 argList = callFuncStmt.find(
'./{*}arg-spec')
2023 container = createElem(
'arg')
2026 callFuncStmt.find(
'./{*}procedure-designator').tail =
'('
2027 argList = createElem(
'arg-spec', tail=
')')
2028 callFuncStmt.append(argList)
2029 item = createExprPart(varNameToUse)
2030 previous = pos - 1
if pos >= 0
else len(argList) + pos
2031 while previous >= 0
and tag(argList[previous])
in (
'C',
'cnt'):
2033 following = pos
if pos > 0
else len(argList) + pos + 1
2034 while following <= len(argList) - 1
and tag(argList[following])
in (
'C',
'cnt'):
2036 if (previous >= 0
and argList[previous].find(
'./{*}arg-N/{*}k')
is not None)
or \
2037 (following <= len(argList) - 1
and
2038 argList[following].find(
'./{*}arg-N/{*}k')
is not None)
or \
2039 following == len(argList):
2045 k = createElem(
'k', text=varName)
2046 argN = createElem(
'arg-N', tail=
'=')
2048 argN.set(
'n', varName)
2049 container.append(argN)
2050 container.append(item)
2051 self.insertInList(pos, container, argList)
2053 if self.
path in stopScopes
or self.tree.isUnderStopScopes(self.
path, stopScopes):
2056 var = self.
varList.findVar(varName, exactScope=
True)
2057 if otherNames
is not None:
2058 vOther = [self.
varList.findVar(v, exactScope=
True)
for v
in otherNames]
2059 vOther = [v
for v
in vOther
if v
is not None]
2065 self.
addVar([[self.
path, varName, declStmt, pos]])
2066 if moduleVarList
is not None:
2069 for (moduleName, moduleVarNames)
in moduleVarList])
2072 if len(self.
path.split(
'/')) == 1:
2073 filename, scopePathInterface = self.tree.findScopeInterface(self.
path)
2074 if filename
is not None:
2077 if self.getFileName() == os.path.normpath(filename):
2079 xml = self.mainScope
2083 filename, parserOptions, wrapH, tree=self.tree,
2084 clsPYFT=self._mainScope.__class__)
2086 scopeInterface = xml.getScopeNode(scopePathInterface)
2087 varInterface = scopeInterface.varList.findVar(varName, exactScope=
True)
2088 if varInterface
is None:
2089 scopeInterface.addVar([[scopePathInterface, varName,
2091 if moduleVarList
is not None:
2094 [(scopePathInterface, moduleName, moduleVarNames)
2095 for (moduleName, moduleVarNames)
in moduleVarList])
2102 if var
is None and self.
path not in stopScopes:
2105 for scopePathUp
in self.tree.calledByScope(self.
path):
2106 if scopePathUp
in stopScopes
or self.tree.isUnderStopScopes(scopePathUp,
2111 for filename
in self.tree.scopeToFiles(scopePathUp):
2114 if self.getFileName() == os.path.normpath(filename):
2116 xml = self.mainScope
2120 filename, parserOptions, wrapH,
2122 clsPYFT=self._mainScope.__class__)
2124 scopeUp = xml.getScopeNode(scopePathUp)
2126 scopeUp.addArgInTree(
2127 varName, declStmt, pos,
2128 stopScopes, moduleVarList, otherNames,
2129 parserOptions=parserOptions,
2132 name = self.
path.split(
'/')[-1].split(
':')[1].upper()
2134 varNameToUse = varName
2135 if otherNames
is not None:
2136 vOther = [scopeUp.varList.findVar(v, exactScope=
True)
2137 for v
in otherNames]
2138 vOther = [v
for v
in vOther
if v
is not None]
2140 varNameToUse = vOther[-1][
'n']
2141 if self.
path.split(
'/')[-1].split(
':')[0] ==
'sub':
2143 for callStmt
in scopeUp.findall(
'.//{*}call-stmt'):
2144 callName = n2name(callStmt.find(
2145 './{*}procedure-designator/{*}named-E/{*}N')).upper()
2146 if callName == name:
2147 insertInArgList(varName, varNameToUse, pos, callStmt)
2151 for funcCall
in scopeUp.findall(
2152 './/{*}named-E/{*}R-LT/{*}parens-R/' +
2153 '{*}element-LT/../../..'):
2154 funcName = n2name(funcCall.find(
'./{*}N')).upper()
2155 if funcName == name:
2156 insertInArgList(varName, varNameToUse, pos, funcCall)
2167 for interface
in self.findall(
'.//{*}interface-construct/{*}' +
2168 'program-unit/{*}subroutine-stmt/' +
2169 '{*}subroutine-N/{*}N/../../../'):
2170 if n2name(interface.find(
'./{*}subroutine-stmt/' +
2171 '{*}subroutine-N/' +
2172 '{*}N')).upper() == name:
2174 raise PYFTError(
'This case is not yet implemented')