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
526 for scope
in self.getScopes():
529 if (scope.path.count(
'/') == 0
or
530 (scope.path.count(
'/') >= 2
and
531 scope.path.split(
'/')[-2].startswith(
'interface:'))):
532 if scope.find(
'./{*}implicit-none-stmt')
is None:
533 message =
"The 'IMPLICIT NONE' statment is missing in file " + \
534 "'{file}' for {scopePath}.".format(file=scope.getFileName(),
535 scopePath=scope.path)
538 logging.error(message)
540 logging.warning(message)
546 Check for missing INTENT attributes on dummy arguments.
548 Issues a logging warning for each dummy argument without an INTENT attribute.
549 When mustRaise is True, logs an error and raises PYFTError.
553 mustRaise : bool, optional
554 If False (default), issue warnings and continue.
555 If True, issue errors and raise PYFTError.
559 >>> pft = PYFT('input.F90')
560 >>> pft.checkIntent() # Issues warning for missing INTENT
561 >>> pft.checkIntent(mustRaise=True) # Raises error
564 log = logging.error
if mustRaise
else logging.warning
566 if var[
'arg']
and var[
'i']
is None:
567 log((
"The dummy argument {} has no INTENT attribute, in " +
568 "file '{}'").format(var[
'n'], self.getFileName()))
570 if not ok
and mustRaise:
571 raise PYFTError(
"There are dummy arguments without INTENT attribute in " +
572 "file '{}'".format(self.getFileName()))
578 Check for missing ONLY clauses in USE statements.
580 Issues a logging warning for each USE statement not followed by an ONLY clause.
581 When mustRaise is True, logs an error and raises PYFTError.
585 mustRaise : bool, optional
586 If False (default), issue warnings and continue.
587 If True, issue errors and raise PYFTError.
591 >>> pft = PYFT('input.F90')
592 >>> pft.checkONLY() # Issues warning for missing ONLY
593 WARNING: USE MODULE is not followed by an ONLY clause...
594 >>> pft.checkONLY(mustRaise=True) # Raises error
597 log = logging.error
if mustRaise
else logging.warning
598 for useStmt
in self.findall(
'.//{*}use-stmt'):
599 module = useStmt.find(
'.//{*}module-N')
600 if module.tail
is None or module.tail.replace(
' ',
'').upper() !=
',ONLY:':
602 log(f
"USE {n2name(module.find('.//{*}N'))} is not followed by an ONLY clause " +
603 f
"in file'{self.getFileName()}'.")
604 if not ok
and mustRaise:
605 raise PYFTError(
"There are USE statements not followed by an ONLY clause " +
606 f
"file '{self.getFileName()}'")
612 Detect USE statements in top‑level modules.
613 Checks each module found at the root of the file. For every
614 module, all ``<use‑stmt>`` elements are reported. When
615 ``mustRaise`` is ``True`` an error is logged and a
616 :class:`PYFTError` is raised; otherwise a warning is produced.
620 mustRaise : bool, optional
621 If ``False`` (default), issue warnings and continue.
622 If ``True``, issue errors and raise :class:`PYFTError`.
625 modules = [s
for s
in self.getScopes(level=1, includeItself=
False)
626 if s.path.startswith(
'module:')]
629 log = logging.error
if mustRaise
else logging.warning
630 for module
in modules:
631 use_elems = module.findall(
'./{*}use-stmt')
636 print(f
"Module {module.path} uses:")
637 for use
in use_elems:
638 print(f
" {alltext(use).strip()}")
642 log(f
"USE statements found in top‑level module {module.path} of file ' \
643 {self.getFileName()}'")
645 routines = [r
for r
in module.getScopes(level=2)
646 if r.path.split(
'/')[-1].startswith((
'sub:',
'func:'))]
648 print(f
" Routines in module {module.path}:")
651 if not ok
and mustRaise:
652 raise PYFTError(f
"There are USE statements in top‑level modules in file ' \
653 {self.getFileName()}'")
659 Check consistency of MERGE-based array dimensions across scopes.
661 For each variable name that has a MERGE-based dimension in any scope,
662 verify that all occurrences of the same variable name use the same
663 MERGE expression (full content inside MERGE()).
667 mustRaise : bool, optional
668 If False (default), issue warnings and continue.
669 If True, issue errors and raise PYFTError.
670 stopScopes : list of str, optional
671 Scope paths to use as roots for call tree filtering.
672 When provided (and the tree is available), only scopes
673 called by these roots are checked.
677 >>> pft = PYFT('input.F90')
678 >>> pft.checkKeyDimConsistency()
679 >>> pft.checkKeyDimConsistency(mustRaise=True)
682 log = logging.error
if mustRaise
else logging.warning
685 for scope
in self.getScopes():
686 if (stopScopes
is not None and
688 if not self.tree.isUnderStopScopes(
689 scope.path, stopScopes, includeStopScopes=
True):
692 if var[
'as']
is None:
694 for dim
in var[
'as']:
696 if upper
is not None and 'MERGE(' in upper.upper():
698 r'^MERGE\((.+)\)$', upper.strip(), re.IGNORECASE)
700 mergeContent = match.group(1)
701 varName = var[
'n'].upper()
702 if varName
not in mergeVars:
703 mergeVars[varName] = {}
704 if mergeContent
not in mergeVars[varName]:
705 mergeVars[varName][mergeContent] = []
706 mergeVars[varName][mergeContent].append(
709 for varName, mergeGroups
in mergeVars.items():
710 if len(mergeGroups) > 1:
711 for mergeContent, scopes
in mergeGroups.items():
712 msg = (f
"Variable {varName} has MERGE dimension "
713 f
"'MERGE({mergeContent})' in {', '.join(scopes)}.")
717 if not ok
and mustRaise:
718 raise PYFTError(
"Inconsistent MERGE-based array dimensions found.")
724 @updateTree('signal')
727 Remove variables from declarations and argument lists.
731 varList : list of tuple
732 List of variables to remove. Each item is a list or tuple of two elements:
733 - First element: scope path where the variable is used (or declared).
734 This is a '/' separated path where each element has the form:
735 'module:<name>', 'sub:<name>', 'func:<name>', or 'type:<name>'.
736 - Second element: variable name (string).
738 Example: [('module:MOD/sub:SUB', 'X'), ('module:MOD/sub:SUB', 'Y')]
739 simplify : bool, optional
740 If True, also remove variables that become unused after the deletion
741 (e.g., kind selectors used only by the removed variable).
745 Remove variable X from subroutine SUB in module MOD:
746 >>> pft = PYFT('input.F90')
747 >>> pft.removeVar([('module:MOD/sub:SUB', 'X')])
749 Remove multiple variables with simplification:
750 >>> pft.removeVar([('module:MOD/sub:SUB', 'KIND_VAR')], simplify=True)
754 - Dummy arguments are removed from both declarations and argument lists.
755 - USE statement variables are removed from ONLY clauses.
756 - If all variables in a declaration statement are removed, the statement
757 itself is deleted (unless simplify=True, which may delete additional unused variables).
763 for scopePath, varName
in varList:
764 nb = scopePath.count(
'/')
765 sortedVarList[nb] = sortedVarList.get(nb, []) + [(scopePath, varName.upper())]
767 varToRemoveIfUnused = []
769 nbList = []
if len(sortedVarList.keys()) == 0
else \
770 range(max(sortedVarList.keys()) + 1)[::-1]
772 sortedVarList[nb] = sortedVarList.get(nb, [])
774 for scopePath
in list(set(scopePath
for scopePath, _
in sortedVarList[nb])):
776 scope = self.mainScope.getScopeNode(scopePath)
778 varNames = list(set(v
for (w, v)
in sortedVarList[nb]
if w == scopePath))
786 for node
in list(scope):
790 dummyList = node.find(
'{*}dummy-arg-LT')
791 if dummyList
is not None:
793 for arg
in dummyList.findall(
'.//{*}arg-N'):
794 name = n2name(arg.find(
'.//{*}N')).upper()
795 for varName
in [v
for v
in varNames
if v == name]:
797 scope.removeFromList(arg, dummyList)
800 if tag(node) == declStmt:
803 declList = node.find(
'./{*}EN-decl-LT')
804 for enDecl
in declList.findall(
'.//{*}EN-decl'):
805 name = n2name(enDecl.find(
'.//{*}N')).upper()
806 for varName
in [v
for v
in varNames
if v == name]:
809 varNames.remove(varName)
810 scope.removeFromList(enDecl, declList)
812 if len(list(declList.findall(
'./{*}EN-decl'))) == 0:
814 varToRemoveIfUnused.extend([[scopePath, n2name(nodeN)]
815 for nodeN
in node.findall(
'.//{*}N')])
818 if previous
is not None and node.tail
is not None:
819 if previous.tail
is None:
821 previous.tail += node.tail
823 scope.getParent(node).remove(node)
826 if tag(node) ==
'use-stmt':
828 useList = node.find(
'./{*}rename-LT')
829 if useList
is not None:
830 for rename
in useList.findall(
'.//{*}rename'):
831 name = n2name(rename.find(
'.//{*}N')).upper()
832 for varName
in [v
for v
in varNames
if v == name]:
833 varNames.remove(varName)
835 scope.removeFromList(rename, useList)
837 attribute = node.find(
'{*}module-N').tail
838 if attribute
is None:
840 attribute = attribute.replace(
' ',
'').replace(
'\n',
'')
841 attribute = attribute.replace(
'&',
'').upper()
842 useList = node.find(
'./{*}rename-LT')
843 if len(useList) == 0
and attribute[0] ==
',' and \
844 attribute[1:] ==
'ONLY:':
847 if previous
is not None and node.tail
is not None:
848 if previous.tail
is None:
850 previous.tail += node.tail
852 scope.getParent(node).remove(node)
853 scope.tree.signal(scope)
854 elif len(useList) == 0:
856 moduleName = scope.getSiblings(useList, before=
True,
858 previousTail = moduleName.tail
859 if previousTail
is not None:
860 moduleName.tail = previousTail.replace(
',',
'')
861 scope.getParent(useList).remove(useList)
863 if len(varNames) == 0:
870 if len(varNames) != 0:
871 newWhere =
'/'.join(scopePath.split(
'/')[:-1])
872 sortedVarList[nb - 1] = sortedVarList.get(nb - 1, []) + \
873 [(newWhere, varName)
for varName
in varNames]
875 if simplify
and len(varToRemoveIfUnused) > 0:
882 Add variables to declarations and argument lists.
886 varList : list of list/tuple
887 List of variable specifications to insert. Each specification is a list
889 - Scope path (str): path to the module, subroutine, function, or type
890 where the variable should be declared (e.g., 'module:MOD/sub:SUB').
891 - Variable name (str): name of the variable to add.
892 - Declaration statement (str): FORTRAN declaration (e.g., 'REAL, INTENT(IN) :: X').
893 - Position (int or None): position in dummy argument list for arguments,
894 None for local variables.
898 Add a local variable:
899 >>> pft = PYFT('input.F90')
900 >>> pft.addVar([('module:MOD/sub:SUB', 'LOCAL_VAR', 'INTEGER :: LOCAL_VAR', None)])
902 Add a dummy argument at position 0:
903 >>> pft.addVar([('module:MOD/sub:SUB', 'ARG', 'REAL, INTENT(IN) :: ARG', 0)])
905 Add multiple variables:
907 ... ('module:MOD/sub:SUB', 'X', 'REAL :: X', None),
908 ... ('module:MOD/sub:SUB', 'Y', 'INTEGER :: Y', None)
913 - If adding to an argument list, the declaration is automatically updated
914 with the INTENT attribute if specified.
915 - Declaration statements are inserted before the first executable statement.
919 for (scopePath, name, declStmt, pos)
in varList:
920 scope = self.getScopeNode(scopePath)
924 argN = createElem(
'arg-N')
925 nodeN = createElem(
'N')
926 nodeN.append(createElem(
'n', text=name))
929 argLst = scope.find(
'.//{*}dummy-arg-LT')
932 scope[0][0].tail =
'('
933 argLst = createElem(
'dummy-arg-LT', tail=
')')
934 scope[0].insert(1, argLst)
935 scope.insertInList(pos, argN, argLst)
940 if declStmt
is not None and declStmt !=
'':
944 if scopePath.split(
'/')[-1].split(
':')[0] ==
'type':
947 ds = createExpr(declStmt)[0]
948 previousTail =
'\n' + declStmt[:re.search(
r'\S', declStmt).start()]
954 ds.tail = scope[-2].tail
955 scope[-2].tail = previousTail
961 ds = createExpr(declStmt)[0]
962 previousTail =
'\n' + declStmt[:re.search(
r'\S', declStmt).start()]
965 declLst = [node
for node
in scope
if tag(node) == declStmtTag]
966 if len(declLst) != 0:
971 index = list(scope).index(decl)
972 if name
in [n2name(nodeN)
for nodeN
in decl.findall(
'.//{*}N')]:
977 stmtLst = [node
for node
in scope
if isExecutable(node)]
978 if len(stmtLst) == 0:
981 index = len(scope) - 1
984 index = list(scope).index(stmtLst[0])
988 ds.tail = scope[index - 1].tail
989 scope[index - 1].tail = previousTail
990 scope.insert(index, ds)
995 @updateTree('signal')
998 Add USE statements for module variables.
1002 moduleVarList : list of list/tuple
1003 List of module variable specifications. Each specification is a list
1005 - Scope path (str): path to the location where USE should be added
1006 (e.g., 'module:MOD/sub:SUB').
1007 - Module name (str): name of the module to USE.
1008 - Variable name(s) (str, list, or None):
1009 - str: single variable name to import.
1010 - list: list of variable names to import.
1011 - None: add USE without ONLY clause (import all).
1015 Import single variable Y from MODD_XX into subroutine FOO:
1016 >>> pft = PYFT('input.F90')
1017 >>> pft.addModuleVar([('sub:FOO', 'MODD_XX', 'Y')])
1018 ! Adds: USE MODD_XX, ONLY: Y
1020 Import multiple variables:
1021 >>> pft.addModuleVar([('sub:FOO', 'MODD_XX', ['X', 'Y', 'Z'])])
1022 ! Adds: USE MODD_XX, ONLY: X, Y, Z
1024 Add USE without ONLY (import all):
1025 >>> pft.addModuleVar([('sub:FOO', 'MODD_XX', None)])
1028 Import into a module:
1029 >>> pft.addModuleVar([('module:MOD', 'OTHER_MOD', 'VAR')])
1033 - Existing USE statements for the same module are updated to include new variables.
1034 - Duplicate imports are avoided (variables already imported are not re-added).
1038 for (scopePath, moduleName, varName)
in moduleVarList:
1041 elif not isinstance(varName, list):
1043 scope = self.getScopeNode(scopePath)
1046 useLst = [node
for node
in scope
if tag(node) ==
'use-stmt']
1051 usName = n2name(us.find(
'.//{*}module-N//{*}N'))
1052 usVar = [n2name(v.find(
'.//{*}N')).upper()
for v
in us.findall(
'.//{*}use-N')]
1053 if len(varName) == 0
and len(usVar) == 0
and usName.upper() == moduleName.upper():
1056 elif len(varName) > 0
and len(usVar) > 0
and usName.upper() == moduleName.upper():
1060 varName = [var
for var
in varName
if var.upper()
not in usVar]
1061 if len(varName) == 0:
1067 stmt = f
'USE {moduleName}'
1068 if len(varName) > 0:
1069 stmt +=
', ONLY: {}'.format(
', '.join(varName))
1070 us = createExpr(stmt)[0]
1073 if len(useLst) != 0:
1075 index = list(scope).index(useLst[-1]) + 1
1080 us.tail = scope[index - 1].tail
1081 scope[index - 1].tail =
'\n'
1082 scope.insert(index, us)
1083 scope.tree.signal(scope)
1088 Display unused variables on stdout.
1090 Searches through all scopes and displays variables that are declared
1091 but never used in the code.
1095 >>> pft = PYFT('input.F90')
1096 >>> pft.showUnusedVar()
1097 Some variables declared in /module:MOD/sub:SUB are unused:
1101 scopes = self.getScopes(excludeKinds=[
'type'])
1102 varUsed = self.
isVarUsed([(scope.path, v[
'n'])
1105 if v[
'scopePath'] == scope.path])
1106 for scope
in scopes:
1107 varList = [k[1].upper()
for (k, v)
in varUsed.items()
if (
not v)
and k[0] == scope.path]
1108 if len(varList) != 0:
1109 print(f
'Some variables declared in {scope.path} are unused:')
1110 print(
' - ' + (
'\n - '.join(varList)))
1115 Check for unused local variables in scopes.
1117 Issues a logging warning for each local variable that is declared but never used.
1118 When mustRaise is True, logs an error and raises PYFTError.
1122 mustRaise : bool, optional
1123 If False (default), issue warnings and continue.
1124 If True, issue errors and raise PYFTError.
1125 excludeList : list of str, optional
1126 List of variable names to exclude from the check.
1127 These variables will not trigger warnings even if unused.
1131 >>> pft = PYFT('input.F90')
1132 >>> pft.checkUnusedLocalVar() # Issues warnings
1133 WARNING: The LOCAL_VAR variable is not used...
1134 >>> pft.checkUnusedLocalVar(excludeList=['TEMP']) # Exclude TEMP
1135 >>> pft.checkUnusedLocalVar(mustRaise=True) # Raises error
1138 if excludeList
is None:
1141 excludeList = [v.upper()
for v
in excludeList]
1142 scopes = self.getScopes(excludeKinds=[
'type'])
1144 varUsed = self.
isVarUsed([(scope.path, v[
'n'])
1147 if (v[
'n'].upper()
not in excludeList
and
1149 v[
'scopePath'].split(
'/')[-1].split(
':')[0] !=
'module' and
1150 v[
'scopePath'] == scope.path)])
1152 for scope
in scopes:
1153 for var
in [k[1].upper()
for (k, v)
in varUsed.items()
1154 if (
not v)
and k[0] == scope.path]:
1155 message = f
"The {var} variable is not used in file " + \
1156 f
"'{scope.getFileName()}' for {scope.path}."
1159 logging.error(message)
1161 logging.warning(message)
1167 Remove unused local variables from declarations.
1169 Removes variables that are declared but never used in the code.
1170 Dummy arguments and module variables are not removed.
1174 excludeList : list of str, optional
1175 List of variable names to exclude from removal.
1176 These variables will be kept even if unused.
1177 simplify : bool, optional
1178 If True, also remove variables that become unused after removal
1179 (e.g., kind selectors).
1183 >>> pft = PYFT('input.F90')
1184 >>> pft.removeUnusedLocalVar() # Remove all unused locals
1186 Remove unused locals except TEMP and COUNTER:
1187 >>> pft.removeUnusedLocalVar(excludeList=['TEMP', 'COUNTER'])
1189 With simplification (remove cascading unused vars):
1190 >>> pft.removeUnusedLocalVar(simplify=True)
1194 - Only local variables (declared in SUBROUTINE/FUNCTION scope) are removed.
1195 - Dummy arguments (subroutine parameters) are preserved.
1196 - Variables imported via USE statements are preserved.
1198 if excludeList
is None:
1201 excludeList = [item.upper()
for item
in excludeList]
1203 allVar = [(scope.path, v[
'n'])
1204 for scope
in self.getScopes(excludeKinds=[
'type'])
1205 for v
in scope.varList
1206 if v[
'n'].upper()
not in excludeList
and v[
'scopePath'] == scope.path]
1207 self.
removeVarIfUnused(allVar, excludeDummy=
True, excludeModule=
True, simplify=simplify)
1212 Replace implicit array bounds with explicit bounds from declarations.
1214 Transforms array slice notation (e.g., A(:)) into explicit bounds
1215 based on the array's declaration.
1219 node : xml element, optional
1220 Specific XML node to transform. If None (default), transforms
1221 all implicit bounds in all scopes.
1225 Given declaration: REAL, DIMENSION(1:10) :: A, B
1226 And usage: A(:) = B(:)
1228 After transformation:
1233 - Only handles 1D array slices (A(:) notation).
1234 - Does not modify allocatable arrays (where ':' is part of declaration).
1235 - Does not modify character type arrays.
1238 nodes = [(scope, scope)
for scope
in self.getScopes()]
1240 nodes = [(self, node)]
1242 for (scope, childNode)
in nodes:
1243 for parent4
in [parent4
for parent4
1244 in childNode.findall(
'.//{*}section-subscript/../../../..')
1245 if parent4.find(
'./{*}R-LT/{*}component-R')
is None]:
1247 for parent
in parent4.findall(
'.//{*}section-subscript/..'):
1248 for sub
in parent.findall(
'.//{*}section-subscript'):
1249 lowerUsed = sub.find(
'./{*}lower-bound')
1250 upperUsed = sub.find(
'./{*}upper-bound')
1255 if lowerUsed
is None or \
1256 (lowerUsed.tail
is not None and ':' in lowerUsed.tail):
1257 if lowerUsed
is None or upperUsed
is None:
1259 varDesc = scope.varList.findVar(n2name(parent4.find(
'.//{*}N')))
1260 if varDesc
is not None and varDesc[
't']
is not None and \
1261 'CHAR' not in varDesc[
't']:
1262 lowerDecl, upperDecl = varDesc[
'as'][list(parent).index(sub)]
1263 if lowerDecl
is None:
1267 lowerBound = lowerDecl
if lowerUsed
is None \
1268 else alltext(lowerUsed)
1269 upperBound = upperDecl
if upperUsed
is None \
1270 else alltext(upperUsed)
1271 if upperBound
is not None:
1272 lowerXml, upperXml = createArrayBounds(lowerBound,
1278 if tag(nnn)
in (
'lower-bound',
'upper-bound'):
1282 "tag is {}".format(tag(nnn)))
1284 sub.extend([lowerXml, upperXml])
1290 Add explicit array parentheses to array variables.
1292 Transforms array variable references to include explicit slice notation.
1293 For example, A becomes A(:) when A is declared as an array.
1297 Given declaration: REAL, DIMENSION(10) :: A, B, C
1298 And usage: A = B + C
1300 After transformation:
1305 - Only modifies known arrays (declared with dimensions).
1306 - Excludes arrays in ALLOCATED, ASSOCIATED, and PRESENT intrinsic calls.
1307 - Does not modify pointer/allocatable arrays in call statements
1308 (to maintain interface compatibility).
1311 for scope
in self.getScopes():
1312 for node
in scope.iter():
1332 nodeToTransform =
None
1333 if tag(node)
in (
'allocate-stmt',
'deallocate-stmt',
'pointer-a-stmt',
1334 'T-decl-stmt',
'associate-stmt',
'function-stmt',
1335 'subroutine-stmt',
'interface-stmt',
'action-stmt',
1339 elif tag(node)
in (
'if-stmt',
'where-stmt',
'forall-stmt'):
1341 part = {
'if-stmt':
'condition-E',
'where-stmt':
'mask-E',
1342 'forall-stmt':
'forall-triplet-spec-LT'}[tag(node)]
1343 nodeToTransform = node.find(
'./{*}' + part)
1344 elif tag(node).endswith(
'-stmt'):
1345 nodeToTransform = node
1346 if nodeToTransform
is not None:
1347 scope.addArrayParenthesesInNode(nodeToTransform)
1352 Add explicit array parentheses to arrays within a specific XML node.
1357 XML node in which to add array parentheses.
1358 Only processes named-E elements within this node.
1362 addArrayParentheses : Add parentheses in all scopes.
1366 >>> pft = PYFT('input.F90')
1367 >>> node = pft.find('.//{*}a-stmt')
1368 >>> pft.addArrayParenthesesInNode(node)
1371 for namedE
in node.findall(
'.//{*}named-E'):
1372 if not namedE.find(
'./{*}R-LT'):
1373 if not self.isNodeInProcedure(namedE, (
'ALLOCATED',
'ASSOCIATED',
'PRESENT')):
1376 nodeN = namedE.find(
'./{*}N')
1377 var = self.
varList.findVar(n2name(nodeN))
1378 if var
is not None and var[
'as']
is not None and len(var[
'as']) > 0
and \
1379 not ((var[
'pointer']
or var[
'allocatable'])
and self.isNodeInCall(namedE)):
1385 nodeRLT = createElem(
'R-LT')
1386 namedE.insert(list(namedE).index(nodeN) + 1, nodeRLT)
1387 arrayR = createElem(
'array-R', text=
'(')
1388 nodeRLT.append(arrayR)
1389 sectionSubscriptLT = createElem(
'section-subscript-LT', tail=
')')
1390 arrayR.append(sectionSubscriptLT)
1392 sectionSubscript = createElem(
'section-subscript', text=
':', tail=
', ')
1393 sectionSubscriptLT.append(sectionSubscript)
1394 sectionSubscript.tail =
None
1399 Remove array parentheses if no index selection is needed.
1401 When an array has full slice notation (e.g., A(:,:)) that matches
1402 the entire array, removes the parentheses.
1407 XML node in which to remove unnecessary parentheses.
1411 Before: A(:,:) (when A is declared as 2D with full bounds)
1416 - Only removes parentheses when all dimensions use full slice (:) notation.
1417 - Preserves parentheses if any dimension has explicit index selection.
1418 - Excludes arrays in ALLOCATED, ASSOCIATED, and PRESENT intrinsic calls.
1421 for namedE
in node.findall(
'.//{*}named-E'):
1422 if namedE.find(
'./{*}R-LT'):
1423 if not self.isNodeInProcedure(namedE, (
'ALLOCATED',
'ASSOCIATED',
'PRESENT')):
1426 nodeN = namedE.find(
'./{*}N')
1427 var = self.
varList.findVar(n2name(nodeN))
1428 if var
is not None and var[
'as']
is not None and len(var[
'as']) > 0
and \
1429 not ((var[
'pointer']
or var[
'allocatable'])
and self.isNodeInCall(namedE)):
1430 arrayR = namedE.findall(
'./{*}R-LT')
1432 sectionSubscriptLT = arr.findall(
'.//{*}section-subscript-LT')
1433 for ss
in sectionSubscriptLT:
1434 lowerBound = ss.findall(
'.//{*}lower-bound')
1435 if len(lowerBound) == 0:
1437 par = self.getParent(ss, level=2)
1438 parOfpar = self.getParent(par)
1439 parOfpar.remove(par)
1445 Transform automatic arrays using customizable templates.
1447 Modifies automatic array declarations in subroutines and functions by
1448 applying templates for declaration, initialization, and cleanup.
1452 declTemplate : str, optional
1453 Template for the array declaration. If None, declaration is unchanged.
1454 startTemplate : str, optional
1455 Template for code to insert as the first executable statement
1457 endTemplate : str, optional
1458 Template for code to insert as the last executable statement
1459 (e.g., deallocation).
1464 Number of arrays modified.
1468 Each template can use the following placeholders (case-sensitive):
1470 ============ ==================================================
1471 Placeholder Description (for declaration "A(I, I:J, 0:I)")
1472 ============ ==================================================
1473 {name} Variable name (e.g., "A")
1474 {type} Type specification (e.g., "REAL")
1475 {doubledotshape} Colon-separated dimensions (e.g., ":, :, :")
1476 {shape} Bounds with indices (e.g., "I, I:J, 0:I")
1477 {lowUpList} Flattened bounds (e.g., "1, I, I, J, 0, I")
1478 ============ ==================================================
1482 Transform automatic arrays to allocatables:
1484 >>> pft = PYFT('input.F90')
1485 >>> pft.modifyAutomaticArrays(
1486 ... declTemplate="{type}, DIMENSION({doubledotshape}), ALLOCATABLE :: {name}",
1487 ... startTemplate="ALLOCATE({name}({shape}))",
1488 ... endTemplate="DEALLOCATE({name})"
1492 REAL, DIMENSION(I, I:J, 0:I) :: A
1496 REAL, DIMENSION(:,:,:), ALLOCATABLE :: A
1497 ALLOCATE(A(I, I:J, 0:I))
1503 - Only processes automatic arrays (stack-allocated based on parameters).
1504 - Excludes dummy arguments, allocatable, pointer, and function result arrays.
1505 - Arrays with initial values are not processed.
1506 - Variables are processed in dependency order to handle interdependencies.
1508 templates = {
'decl': declTemplate
if declTemplate
is not None else '',
1509 'start': startTemplate
if startTemplate
is not None else '',
1510 'end': endTemplate
if endTemplate
is not None else ''}
1513 for scope
in [scope
for scope
in self.getScopes()
1514 if scope.path.split(
'/')[-1].split(
':')[0]
in (
'sub',
'func')]:
1517 varListToTransform = []
1518 for var
in [var
for var
in scope.varList
1519 if var[
'as']
is not None and
1520 len(var[
'as']) > 0
and
1521 'CHARACTER' not in var[
't']
and
1522 not (var[
'arg']
or var[
'allocatable']
or
1523 var[
'pointer']
or var[
'result'])]:
1526 if var[
'init']
is None:
1528 varListToTransform.append(var)
1532 orderedVarListToTransform = []
1533 while len(varListToTransform) > 0:
1535 for var
in varListToTransform[:]:
1537 listN = [x
for dim
in var[
'asx']
for x
in dim
if x
is not None]
1538 listN = [n2name(nodeN).upper()
for asx
in listN
1539 for nodeN
in asx.findall(
'.//{*}N/{*}n/..')]
1540 if len(set(listN).intersection([v[
'n'].upper()
1541 for v
in varListToTransform])) == 0:
1543 varListToTransform.remove(var)
1544 orderedVarListToTransform.append(var)
1547 raise PYFTError(
'It seems that there is a circular reference in ' +
1548 'the declaration statements')
1550 for var
in orderedVarListToTransform[::-1]:
1553 templ = copy.deepcopy(templates)
1554 for templPart
in templ:
1555 if '{doubledotshape}' in templ[templPart]:
1556 templ[templPart] = templ[templPart].replace(
1557 '{doubledotshape}',
','.join([
':'] * len(var[
'as'])))
1558 if '{shape}' in templ[templPart]:
1560 for i
in range(len(var[
'as'])):
1561 if var[
'as'][i][0]
is None:
1562 result.append(var[
'as'][i][1])
1564 result.append(var[
'as'][i][0] +
':' + var[
'as'][i][1])
1565 templ[templPart] = templ[templPart].replace(
'{shape}',
', '.join(result))
1566 if '{name}' in templ[templPart]:
1567 templ[templPart] = templ[templPart].replace(
'{name}', var[
'n'])
1568 if '{type}' in templ[templPart]:
1569 templ[templPart] = templ[templPart].replace(
'{type}', var[
't'])
1570 if '{lowUpList}' in templ[templPart]:
1572 for i
in range(len(var[
'as'])):
1573 if var[
'as'][i][0]
is None:
1574 result.extend([1, var[
'as'][i][1]])
1576 result.extend([var[
'as'][i][0], var[
'as'][i][1]])
1577 templ[templPart] = templ[templPart].replace(
1578 '{lowUpList}',
', '.join([str(r)
for r
in result]))
1581 separator =
"!ABCDEFGHIJKLMNOPQRSTUVWabcdefghijklmnopqrstuvwxyz0123456789"
1583 for node
in createExpr(templ[
'decl'] +
'\n' + separator +
'\n' +
1584 templ[
'start'] +
'\n' + separator +
'\n' +
1586 templPart = list(templ.keys())[part]
1587 if not isinstance(templ[templPart], list):
1588 templ[templPart] = []
1589 if tag(node) ==
'C' and node.text == separator:
1592 templ[templPart].append(node)
1598 for decl
in scope.findall(
'./{*}T-decl-stmt'):
1599 index = list(scope).index(decl)
1600 if var[
'n']
in [n2name(nodeN)
for nodeN
in decl.findall(
'.//{*}N')]:
1602 scope.removeVar([(scope.path, var[
'n'])], simplify=
False)
1603 for nnn
in templ[
'decl'][::-1]:
1604 scope.insert(index, nnn)
1607 for nnn
in templ[
'start'][::-1]:
1608 scope.insertStatement(nnn,
True)
1609 for nnn
in templ[
'end'][::-1]:
1610 scope.insertStatement(nnn,
False)
1617 :param varSpec: a variable description, same form as the items return by self.varList
1618 :param implicitDeclaration: True if the variable may contain implicit declaration
1619 (e.g. outside of PHYEX)
1620 :return: the associated declarative statement
1622 if varSpec[
'use']
is not False:
1623 stmt = f
"USE {varSpec['use']}, ONLY: {varSpec['n']}"
1627 stmt +=
', DIMENSION('
1629 for el
in varSpec[
'as']:
1630 if el[0]
is None and el[1]
is None:
1635 dl.append(el[0] +
':' + el[1])
1636 if (varSpec[
'allocatable']
and not all(d ==
':' for d
in dl))
or \
1637 (any(d ==
':' for d
in dl)
and not varSpec[
'allocatable']):
1638 if not implicitDeclaration:
1639 raise PYFTError(
'Missing dim are mandatory and allowed ' +
1640 'only for allocatable arrays')
1641 stmt +=
', '.join(dl) +
')'
1642 if varSpec[
'allocatable']:
1643 stmt +=
", ALLOCATABLE"
1644 if varSpec[
'parameter']:
1645 stmt +=
", PARAMETER"
1646 if varSpec[
'i']
is not None:
1647 stmt +=
", INTENT(" + varSpec[
'i'] +
")"
1648 if varSpec[
'opt']
is True:
1649 stmt +=
", OPTIONAL"
1650 stmt +=
" :: " + varSpec[
'n']
1651 if varSpec[
'init']
is not None:
1652 stmt +=
"=" + varSpec[
'init']
1658 Find bounds and loop variable for a given array index
1659 :param arr: array node (named-E node with a array-R child)
1660 :param index: index of the rank of the array
1661 :param loopVar: None to create new variable for each added DO loop
1662 or a function that return the name of the variable to use for the
1664 This function returns a string (name of the variable), or True to create
1665 a new variable, or False to not transform this statement
1666 The functions takes as arguments:
1667 - lower and upper bounds as defined in the declaration statement
1668 - lower and upper bounds as given in the statement
1671 :return: the tuple (loopName, lowerBound, upperBound) where:
1672 loopName is the name of the variable to use for the loop
1673 lower and upper bounds are the bounds to use for the DO loop
1676 - False to discard this index
1677 - True to create a new variable to loop with
1679 name = n2name(arr.find(
'./{*}N'))
1680 ss = arr.findall(
'./{*}R-LT/{*}array-R/{*}section-subscript-LT/{*}section-subscript')[index]
1683 if ':' in alltext(ss):
1685 lowerUsed = ss.find(
'./{*}lower-bound')
1686 upperUsed = ss.find(
'./{*}upper-bound')
1687 varDesc = self.
varList.findVar(name, array=
True)
1688 if varDesc
is not None:
1689 lowerDecl, upperDecl = varDesc[
'as'][index]
1690 if lowerDecl
is None:
1693 lowerDecl, upperDecl =
None,
None
1699 varName = lowerDecl
is not None and upperDecl
is not None
1701 varName = loopVar(lowerDecl, upperDecl,
1702 None if lowerUsed
is None else alltext(lowerUsed),
1703 None if upperUsed
is None else alltext(upperUsed), name, index)
1705 lowerDecl
if lowerUsed
is None else alltext(lowerUsed),
1706 upperDecl
if upperUsed
is None else alltext(upperUsed))
1712 Transform a array-R into a parens-R node by replacing slices by variables
1713 In 'A(:)', the ':' is in a array-R node whereas in 'A(JL)', 'JL' is in a parens-R node.
1714 Both the array-R and the parens-R nodes are inside a R-LT node
1715 :param namedE: a named-E node
1716 :param table: dictionnary returned by the decode function
1717 :param varList: None or a VarList object in which varaibles are searched for
1740 nodeRLT = namedE.find(
'./{*}R-LT')
1741 arrayR = nodeRLT.find(
'./{*}array-R')
1742 if arrayR
is not None:
1743 index = list(nodeRLT).index(arrayR)
1744 parensR = createElem(
'parens-R', text=
'(', tail=
')')
1745 elementLT = createElem(
'element-LT')
1746 parensR.append(elementLT)
1748 for ss
in nodeRLT[index].findall(
'./{*}section-subscript-LT/{*}section-subscript'):
1749 element = createElem(
'element', tail=
', ')
1750 elementLT.append(element)
1751 if ':' in alltext(ss):
1753 varName = list(table.keys())[ivar]
1754 lower = ss.find(
'./{*}lower-bound')
1755 upper = ss.find(
'./{*}upper-bound')
1756 if lower
is not None:
1757 lower = alltext(lower)
1758 if upper
is not None:
1759 upper = alltext(upper)
1760 if lower
is not None and ss.text
is not None and ':' in ss.text:
1764 if lower
is None and upper
is None:
1768 element.append(createExprPart(varName))
1775 lower = self.
varList.findVar(n2name(namedE.find(
'{*}N')),
1776 array=
True)[
'as'][ivar][0]
1781 upper = self.
varList.findVar(n2name(namedE.find(
'{*}N')),
1782 array=
True)[
'as'][ivar][1]
1788 newlower = simplifyExpr(lower, add=varName, sub=table[varName][0])
1789 newupper = simplifyExpr(upper, add=varName, sub=table[varName][1])
1790 if newlower != newupper:
1791 raise PYFTError((
"Don't know how to do with an array declared with " +
1792 "'{la}:{ua}' and a loop from '{ll}' to '{ul}'"
1793 ).format(la=lower, ua=upper,
1794 ll=table[varName][0],
1795 ul=table[varName][1]))
1796 element.append(createExprPart(newlower))
1798 element.append(ss.find(
'./{*}lower-bound'))
1800 nodeRLT.remove(nodeRLT[index])
1801 nodeRLT.insert(index, parensR)
1806 Find bounds and loop variable given an array
1807 :param arr: array node (named-E node with a array-R child)
1808 :param loopVar: None to create new variable for each added DO loop
1809 or a function that return the name of the variable to use for the loop
1811 This function returns a string (name of the variable), or True to create
1812 a new variable, or False to not transform this statement
1813 The functions takes as arguments:
1814 - lower and upper bounds as defined in the declaration statement
1815 - lower and upper bounds as given in the statement
1818 :param extraVarList: None or list of variables (such as those contained in a VarList object)
1819 defined but not yet available in the self.varList object.
1820 :return: the tuple (table, newVar) where:
1821 table is a dictionnary: keys are loop variable names
1822 values are tuples with lower and upper bounds
1823 newVar is a list of loop variables not found in varList. This list has the same
1824 format as the varList list.
1826 In case the loop variable cannot be defined, the function returns (None, [])
1829 name = n2name(arr.find(
'./{*}N'))
1831 extraVarList = extraVarList
if extraVarList
is not None else []
1834 for iss, ss
in enumerate(arr.findall(
'./{*}R-LT/{*}array-R/{*}section-subscript-LT/' +
1835 '{*}section-subscript')):
1838 if ':' in alltext(ss):
1843 if varName
is not False and varName
in table:
1844 raise PYFTError((
"The variable {var} must be used for the rank #{i1} whereas " +
1845 "it is already used for rank #{i2} (for array {name})."
1846 ).format(var=varName, i1=str(iss),
1847 i2=str(list(table.keys()).index(varName)),
1858 varName =
'J' + str(j)
1859 var = self.
varList.findVar(varName, extraVarList=extraVarList + varNew)
1860 if (var
is None or var.get(
'new',
False))
and varName
not in table:
1862 varDesc = {
'as': [],
'asx': [],
'n': varName,
'i':
None,
1863 't':
'INTEGER',
'arg':
False,
'use':
False,
'opt':
False,
1864 'scopePath': self.
path}
1865 if varDesc
not in varNew:
1866 varNew.append(varDesc)
1868 elif (varName
is not False and
1869 self.
varList.findVar(varName, array=
False, exactScope=
True)
is None):
1871 varDesc = {
'as': [],
'asx': [],
'n': varName,
'i':
None,
1872 't':
'INTEGER',
'arg':
False,
'use':
False,
'opt':
False,
1873 'scopePath': self.
path}
1874 varNew.append(varDesc)
1877 table[varName] = (lower, upper)
1879 return (
None, [])
if False in table
else (table, varNew)
1885 :param oldName: old name of the variable
1886 :param newName: new name of the variable
1888 for node
in self.findall(
'.//{*}N'):
1889 if n2name(node).upper() == oldName.upper():
1891 for nnn
in node.findall(
'./{*}n')[1:]:
1894 node.find(
'./{*}n').text = newName
1899 :param varList: list of variables to remove if unused. Each item is a list or tuple of two
1901 The first one describes where the variable is used, the second one is
1902 the name of the variable. The first element is a '/'-separated path with
1903 each element having the form 'module:<name of the module>',
1904 'sub:<name of the subroutine>' or 'func:<name of the function>'
1905 :param excludeDummy: if True, dummy arguments are always kept untouched
1906 :param excludeModule: if True, module variables are always kept untouched
1907 :param simplify: try to simplify code (if we delete a declaration statement that used a
1908 variable as kind selector, and if this variable is not used else where,
1910 :return: the varList without the unremovable variables
1911 If possible, remove the variable from declaration, and from the argument list if needed
1915 varList = [v
for v
in varList
if v[0].split(
'/')[-1].split(
':')[0] !=
'module']
1917 varUsed = self.
isVarUsed(varList, dummyAreAlwaysUsed=excludeDummy)
1918 varListToRemove = []
1919 for scopePath, varName
in varList:
1920 assert scopePath.split(
'/')[-1].split(
':')[0] !=
'type', \
1921 "The removeVarIfUnused cannot be used with type members"
1922 if not varUsed[(scopePath, varName)]:
1923 varListToRemove.append([scopePath, varName])
1924 self.
removeVar(varListToRemove, simplify=simplify)
1925 return varListToRemove
1928 def isVarUsed(self, varList, exactScope=False, dummyAreAlwaysUsed=False):
1930 :param varList: list of variables to test. Each item is a list or tuple of two elements.
1931 The first one describes where the variable is declared, the second one is
1932 the name of the variable. The first element is a '/'-separated path with
1933 each element having the form 'module:<name of the module>',
1934 'sub:<name of the subroutine>' or 'func:<name of the function>'
1935 :param exactScope: True to search strictly in scope
1936 :param dummyAreAlwaysUsed: Returns True if variable is a dummy argument
1937 :return: a dict whose keys are the elements of varList, and values are True when the
1938 variable is used, False otherwise
1940 If exactScope is True, the function will search for variable usage
1941 only in this scope. But this feature has a limited interest.
1943 If exactScope is False:
1944 - if scopePath is a subroutine/function in a contains section,
1945 and if the variable is not declared in this scope, usages are
1946 searched in the module/subroutine/function upper that declared
1947 the variable and in all subroutines/functions in the contains section
1948 - if scopePath is a module/subroutine/function that has a
1949 contains sections, usages are searched in all subroutines/functions
1950 in the contains section
1952 To know if a variable can be removed, you must use exactScope=False
1956 allScopes = {scope.path: scope
for scope
in self.mainScope.getScopes()}
1960 locsVar = {(scopePath, varName): [scopePath]
1961 for scopePath, varName
in varList}
1964 for scopePath, varName
in varList:
1967 var = allScopes[scopePath].varList.findVar(varName)
1968 path = scopePath.split(
'/')[0]
if var
is None else var[
'scopePath']
1973 for scPath, sc
in allScopes.items():
1974 if scPath.startswith(path +
'/')
and \
1975 scPath.split(
'/')[-1].split(
':')[0] !=
'type':
1977 if sc.varList.findVar(varName, exactScope=
True)
is None:
1979 testScopes.append(scPath)
1980 locsVar[(scopePath, varName)] = testScopes
1984 for scopePath
in list(set(item
for sublist
in locsVar.values()
for item
in sublist)):
1985 usedVar[scopePath] = []
1987 for node
in allScopes[scopePath]:
1990 if not tag(node) ==
'use-stmt':
1991 if tag(node) ==
'T-decl-stmt':
1995 nodesN = node.findall(
'.//{*}_T-spec_//{*}N') + \
1996 node.findall(
'.//{*}shape-spec//{*}N')
1998 nodesN = node.findall(
'.//{*}N')
2001 for nodeN
in nodesN:
2002 if dummyAreAlwaysUsed:
2006 usedVar[scopePath].append(n2name(nodeN).upper())
2008 parPar = allScopes[scopePath].getParent(nodeN, 2)
2011 if parPar
is None or not tag(parPar) ==
'dummy-arg-LT':
2012 usedVar[scopePath].append(n2name(nodeN).upper())
2015 for scopePath, varName
in varList:
2016 assert scopePath.split(
'/')[-1].split(
':')[0] !=
'type', \
2017 'We cannot check type component usage'
2018 result[(scopePath, varName)] = any(varName.upper()
in usedVar[scopePath]
2019 for scopePath
in locsVar[(scopePath, varName)])
2025 @updateTree('signal')
2026 def addONLY(self, parserOptions=None, wrapH=False):
2028 Adds missing ONLY clause to USE statements
2029 :param parserOptions, wrapH: see the PYFT class
2031 for useStmt
in self.findall(
'.//{*}use-stmt'):
2032 module = useStmt.find(
'.//{*}module-N')
2033 if module.tail
is None or module.tail.replace(
' ',
'').upper() !=
',ONLY:':
2035 modulename = n2name(module.find(
'.//{*}N')).upper()
2036 modulescope =
'module:' + modulename
2037 modulefile = self.tree.scopeToFiles(modulescope)
2038 if len(modulefile) != 1:
2039 raise PYFTError(f
'No or several files define the {modulename} module')
2044 if self.getFileName() == os.path.normpath(modulefile[0]):
2046 xml = self.mainScope
2050 modulefile[0], parserOptions, wrapH, tree=self.tree,
2051 clsPYFT=self._mainScope.__class__)
2055 symbols.extend(v[
'n']
for v
in xml.varList.restrict(modulescope,
True))
2057 for scope
in xml.getScopeNode(modulescope,
2058 excludeContains=
False).getScopes(
2060 excludeContains=
False,
2061 includeItself=
False):
2062 if scope.path.split(
'/')[1].split(
':')[0] ==
'interface':
2063 if scope.path.split(
'/')[1].split(
':')[1] !=
'--UNKNOWN--':
2065 symbols.append(scope.path.split(
'/')[1].split(
':')[1])
2066 if len(scope.path.split(
'/')) == 3:
2068 symbols.append(scope.path.split(
'/')[2].split(
':')[1])
2070 symbols.append(scope.path.split(
'/')[1].split(
':')[1])
2071 symbols = sorted(set(symbols))
2078 module.tail =
', ONLY: '
2079 renameLT = createElem(
'rename-LT')
2080 useStmt.append(renameLT)
2082 parent = self.getScopePath(useStmt)
2083 isVarUsed = self.
isVarUsed([(parent, symbol.upper())
for symbol
in symbols])
2084 for symbol
in symbols:
2085 if isVarUsed[(parent, symbol.upper())]:
2086 useN = createElem(
'use-N', childs=createElem(
'N',
2087 childs=createElem(
'n', text=symbol)))
2088 rename = createElem(
'rename', tail=
', ', childs=useN)
2089 renameLT.append(rename)
2092 previous = self.getSiblings(useStmt, before=
True, after=
False)
2093 previous =
None if len(previous) == 0
else previous[-1]
2094 if previous
is not None and useStmt.tail
is not None:
2095 if previous.tail
is None:
2097 previous.tail += useStmt.tail
2098 self.getParent(useStmt).remove(useStmt)
2099 self.tree.signal(self)
2109 def addArgInTree(self, varName, declStmt, pos, stopScopes, moduleVarList=None,
2111 parserOptions=None, wrapH=False):
2113 Adds an argument to the routine and propagates it upward until we encounter a scope
2114 where the variable exists or a scope in stopScopes
2115 :param varName: variable name
2116 :param declStmt: declarative statment (will be used by addVar)
2117 :param pos: position of the variable in the list of dummy argument
2118 :param stopScopes: list of scopes to reach
2119 :param moduleVarList: list of module variable specification to insert in the xml code
2120 a module variable specification is a list of two elements:
2122 - variable name or or list of variable names
2123 or None to add a USE statement without the ONLY attribute
2124 use moduleVarList to not add module variables
2125 :param otherNames: None or list of other variable names that can be used
2126 These variables are used first
2127 :param parserOptions, wrapH: see the PYFT class
2129 Argument is inserted only on paths leading to one of scopes listed in stopScopes
2131 def insertInArgList(varName, varNameToUse, pos, callFuncStmt):
2133 Insert varName in the list of arguments to the subroutine or function call
2134 :param varName: name of the dummy argument
2135 :param varNameToUse: name of the variable
2136 :param pos: inclusion position
2137 :param callFuncStmt: call statement or function call
2139 argList = callFuncStmt.find(
'./{*}R-LT/{*}parens-R/{*}element-LT')
2140 if argList
is not None:
2141 container = createElem(
'element')
2143 argList = callFuncStmt.find(
'./{*}arg-spec')
2144 container = createElem(
'arg')
2147 callFuncStmt.find(
'./{*}procedure-designator').tail =
'('
2148 argList = createElem(
'arg-spec', tail=
')')
2149 callFuncStmt.append(argList)
2150 item = createExprPart(varNameToUse)
2151 previous = pos - 1
if pos >= 0
else len(argList) + pos
2152 while previous >= 0
and tag(argList[previous])
in (
'C',
'cnt'):
2154 following = pos
if pos > 0
else len(argList) + pos + 1
2155 while following <= len(argList) - 1
and tag(argList[following])
in (
'C',
'cnt'):
2157 if (previous >= 0
and argList[previous].find(
'./{*}arg-N/{*}k')
is not None)
or \
2158 (following <= len(argList) - 1
and
2159 argList[following].find(
'./{*}arg-N/{*}k')
is not None)
or \
2160 following == len(argList):
2166 k = createElem(
'k', text=varName)
2167 argN = createElem(
'arg-N', tail=
'=')
2169 argN.set(
'n', varName)
2170 container.append(argN)
2171 container.append(item)
2172 self.insertInList(pos, container, argList)
2174 if self.
path in stopScopes
or self.tree.isUnderStopScopes(self.
path, stopScopes):
2177 var = self.
varList.findVar(varName, exactScope=
True)
2178 if otherNames
is not None:
2179 vOther = [self.
varList.findVar(v, exactScope=
True)
for v
in otherNames]
2180 vOther = [v
for v
in vOther
if v
is not None]
2186 self.
addVar([[self.
path, varName, declStmt, pos]])
2187 if moduleVarList
is not None:
2190 for (moduleName, moduleVarNames)
in moduleVarList])
2193 if len(self.
path.split(
'/')) == 1:
2194 filename, scopePathInterface = self.tree.findScopeInterface(self.
path)
2195 if filename
is not None:
2198 if self.getFileName() == os.path.normpath(filename):
2200 xml = self.mainScope
2204 filename, parserOptions, wrapH, tree=self.tree,
2205 clsPYFT=self._mainScope.__class__)
2207 scopeInterface = xml.getScopeNode(scopePathInterface)
2208 varInterface = scopeInterface.varList.findVar(varName, exactScope=
True)
2209 if varInterface
is None:
2210 scopeInterface.addVar([[scopePathInterface, varName,
2212 if moduleVarList
is not None:
2215 [(scopePathInterface, moduleName, moduleVarNames)
2216 for (moduleName, moduleVarNames)
in moduleVarList])
2223 if var
is None and self.
path not in stopScopes:
2226 for scopePathUp
in self.tree.calledByScope(self.
path):
2227 if scopePathUp
in stopScopes
or self.tree.isUnderStopScopes(scopePathUp,
2232 for filename
in self.tree.scopeToFiles(scopePathUp):
2235 if self.getFileName() == os.path.normpath(filename):
2237 xml = self.mainScope
2241 filename, parserOptions, wrapH,
2243 clsPYFT=self._mainScope.__class__)
2245 scopeUp = xml.getScopeNode(scopePathUp)
2247 scopeUp.addArgInTree(
2248 varName, declStmt, pos,
2249 stopScopes, moduleVarList, otherNames,
2250 parserOptions=parserOptions,
2253 name = self.
path.split(
'/')[-1].split(
':')[1].upper()
2255 varNameToUse = varName
2256 if otherNames
is not None:
2257 vOther = [scopeUp.varList.findVar(v, exactScope=
True)
2258 for v
in otherNames]
2259 vOther = [v
for v
in vOther
if v
is not None]
2261 varNameToUse = vOther[-1][
'n']
2262 if self.
path.split(
'/')[-1].split(
':')[0] ==
'sub':
2264 for callStmt
in scopeUp.findall(
'.//{*}call-stmt'):
2265 callName = n2name(callStmt.find(
2266 './{*}procedure-designator/{*}named-E/{*}N')).upper()
2267 if callName == name:
2268 insertInArgList(varName, varNameToUse, pos, callStmt)
2272 for funcCall
in scopeUp.findall(
2273 './/{*}named-E/{*}R-LT/{*}parens-R/' +
2274 '{*}element-LT/../../..'):
2275 funcName = n2name(funcCall.find(
'./{*}N')).upper()
2276 if funcName == name:
2277 insertInArgList(varName, varNameToUse, pos, funcCall)
2288 for interface
in self.findall(
'.//{*}interface-construct/{*}' +
2289 'program-unit/{*}subroutine-stmt/' +
2290 '{*}subroutine-N/{*}N/../../../'):
2291 if n2name(interface.find(
'./{*}subroutine-stmt/' +
2292 '{*}subroutine-N/' +
2293 '{*}N')).upper() == name:
2295 raise PYFTError(
'This case is not yet implemented')