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 Check consistency of MERGE-based array dimensions across scopes.
614 For each variable name that has a MERGE-based dimension in any scope,
615 verify that all occurrences of the same variable name use the same
616 MERGE expression (full content inside MERGE()).
620 mustRaise : bool, optional
621 If False (default), issue warnings and continue.
622 If True, issue errors and raise PYFTError.
623 stopScopes : list of str, optional
624 Scope paths to use as roots for call tree filtering.
625 When provided (and the tree is available), only scopes
626 called by these roots are checked.
630 >>> pft = PYFT('input.F90')
631 >>> pft.checkKeyDimConsistency()
632 >>> pft.checkKeyDimConsistency(mustRaise=True)
635 log = logging.error
if mustRaise
else logging.warning
638 for scope
in self.getScopes():
639 if (stopScopes
is not None and
641 if not self.tree.isUnderStopScopes(
642 scope.path, stopScopes, includeStopScopes=
True):
645 if var[
'as']
is None:
647 for dim
in var[
'as']:
649 if upper
is not None and 'MERGE(' in upper.upper():
651 r'^MERGE\((.+)\)$', upper.strip(), re.IGNORECASE)
653 mergeContent = match.group(1)
654 varName = var[
'n'].upper()
655 if varName
not in mergeVars:
656 mergeVars[varName] = {}
657 if mergeContent
not in mergeVars[varName]:
658 mergeVars[varName][mergeContent] = []
659 mergeVars[varName][mergeContent].append(
662 for varName, mergeGroups
in mergeVars.items():
663 if len(mergeGroups) > 1:
664 for mergeContent, scopes
in mergeGroups.items():
665 msg = (f
"Variable {varName} has MERGE dimension "
666 f
"'MERGE({mergeContent})' in {', '.join(scopes)}.")
670 if not ok
and mustRaise:
671 raise PYFTError(
"Inconsistent MERGE-based array dimensions found.")
677 @updateTree('signal')
680 Remove variables from declarations and argument lists.
684 varList : list of tuple
685 List of variables to remove. Each item is a list or tuple of two elements:
686 - First element: scope path where the variable is used (or declared).
687 This is a '/' separated path where each element has the form:
688 'module:<name>', 'sub:<name>', 'func:<name>', or 'type:<name>'.
689 - Second element: variable name (string).
691 Example: [('module:MOD/sub:SUB', 'X'), ('module:MOD/sub:SUB', 'Y')]
692 simplify : bool, optional
693 If True, also remove variables that become unused after the deletion
694 (e.g., kind selectors used only by the removed variable).
698 Remove variable X from subroutine SUB in module MOD:
699 >>> pft = PYFT('input.F90')
700 >>> pft.removeVar([('module:MOD/sub:SUB', 'X')])
702 Remove multiple variables with simplification:
703 >>> pft.removeVar([('module:MOD/sub:SUB', 'KIND_VAR')], simplify=True)
707 - Dummy arguments are removed from both declarations and argument lists.
708 - USE statement variables are removed from ONLY clauses.
709 - If all variables in a declaration statement are removed, the statement
710 itself is deleted (unless simplify=True, which may delete additional unused variables).
716 for scopePath, varName
in varList:
717 nb = scopePath.count(
'/')
718 sortedVarList[nb] = sortedVarList.get(nb, []) + [(scopePath, varName.upper())]
720 varToRemoveIfUnused = []
722 nbList = []
if len(sortedVarList.keys()) == 0
else \
723 range(max(sortedVarList.keys()) + 1)[::-1]
725 sortedVarList[nb] = sortedVarList.get(nb, [])
727 for scopePath
in list(set(scopePath
for scopePath, _
in sortedVarList[nb])):
729 scope = self.mainScope.getScopeNode(scopePath)
731 varNames = list(set(v
for (w, v)
in sortedVarList[nb]
if w == scopePath))
739 for node
in list(scope):
743 dummyList = node.find(
'{*}dummy-arg-LT')
744 if dummyList
is not None:
746 for arg
in dummyList.findall(
'.//{*}arg-N'):
747 name = n2name(arg.find(
'.//{*}N')).upper()
748 for varName
in [v
for v
in varNames
if v == name]:
750 scope.removeFromList(arg, dummyList)
753 if tag(node) == declStmt:
756 declList = node.find(
'./{*}EN-decl-LT')
757 for enDecl
in declList.findall(
'.//{*}EN-decl'):
758 name = n2name(enDecl.find(
'.//{*}N')).upper()
759 for varName
in [v
for v
in varNames
if v == name]:
762 varNames.remove(varName)
763 scope.removeFromList(enDecl, declList)
765 if len(list(declList.findall(
'./{*}EN-decl'))) == 0:
767 varToRemoveIfUnused.extend([[scopePath, n2name(nodeN)]
768 for nodeN
in node.findall(
'.//{*}N')])
771 if previous
is not None and node.tail
is not None:
772 if previous.tail
is None:
774 previous.tail += node.tail
776 scope.getParent(node).remove(node)
779 if tag(node) ==
'use-stmt':
781 useList = node.find(
'./{*}rename-LT')
782 if useList
is not None:
783 for rename
in useList.findall(
'.//{*}rename'):
784 name = n2name(rename.find(
'.//{*}N')).upper()
785 for varName
in [v
for v
in varNames
if v == name]:
786 varNames.remove(varName)
788 scope.removeFromList(rename, useList)
790 attribute = node.find(
'{*}module-N').tail
791 if attribute
is None:
793 attribute = attribute.replace(
' ',
'').replace(
'\n',
'')
794 attribute = attribute.replace(
'&',
'').upper()
795 useList = node.find(
'./{*}rename-LT')
796 if len(useList) == 0
and attribute[0] ==
',' and \
797 attribute[1:] ==
'ONLY:':
800 if previous
is not None and node.tail
is not None:
801 if previous.tail
is None:
803 previous.tail += node.tail
805 scope.getParent(node).remove(node)
806 scope.tree.signal(scope)
807 elif len(useList) == 0:
809 moduleName = scope.getSiblings(useList, before=
True,
811 previousTail = moduleName.tail
812 if previousTail
is not None:
813 moduleName.tail = previousTail.replace(
',',
'')
814 scope.getParent(useList).remove(useList)
816 if len(varNames) == 0:
823 if len(varNames) != 0:
824 newWhere =
'/'.join(scopePath.split(
'/')[:-1])
825 sortedVarList[nb - 1] = sortedVarList.get(nb - 1, []) + \
826 [(newWhere, varName)
for varName
in varNames]
828 if simplify
and len(varToRemoveIfUnused) > 0:
835 Add variables to declarations and argument lists.
839 varList : list of list/tuple
840 List of variable specifications to insert. Each specification is a list
842 - Scope path (str): path to the module, subroutine, function, or type
843 where the variable should be declared (e.g., 'module:MOD/sub:SUB').
844 - Variable name (str): name of the variable to add.
845 - Declaration statement (str): FORTRAN declaration (e.g., 'REAL, INTENT(IN) :: X').
846 - Position (int or None): position in dummy argument list for arguments,
847 None for local variables.
851 Add a local variable:
852 >>> pft = PYFT('input.F90')
853 >>> pft.addVar([('module:MOD/sub:SUB', 'LOCAL_VAR', 'INTEGER :: LOCAL_VAR', None)])
855 Add a dummy argument at position 0:
856 >>> pft.addVar([('module:MOD/sub:SUB', 'ARG', 'REAL, INTENT(IN) :: ARG', 0)])
858 Add multiple variables:
860 ... ('module:MOD/sub:SUB', 'X', 'REAL :: X', None),
861 ... ('module:MOD/sub:SUB', 'Y', 'INTEGER :: Y', None)
866 - If adding to an argument list, the declaration is automatically updated
867 with the INTENT attribute if specified.
868 - Declaration statements are inserted before the first executable statement.
872 for (scopePath, name, declStmt, pos)
in varList:
873 scope = self.getScopeNode(scopePath)
877 argN = createElem(
'arg-N')
878 nodeN = createElem(
'N')
879 nodeN.append(createElem(
'n', text=name))
882 argLst = scope.find(
'.//{*}dummy-arg-LT')
885 scope[0][0].tail =
'('
886 argLst = createElem(
'dummy-arg-LT', tail=
')')
887 scope[0].insert(1, argLst)
888 scope.insertInList(pos, argN, argLst)
893 if declStmt
is not None and declStmt !=
'':
897 if scopePath.split(
'/')[-1].split(
':')[0] ==
'type':
900 ds = createExpr(declStmt)[0]
901 previousTail =
'\n' + declStmt[:re.search(
r'\S', declStmt).start()]
907 ds.tail = scope[-2].tail
908 scope[-2].tail = previousTail
914 ds = createExpr(declStmt)[0]
915 previousTail =
'\n' + declStmt[:re.search(
r'\S', declStmt).start()]
918 declLst = [node
for node
in scope
if tag(node) == declStmtTag]
919 if len(declLst) != 0:
924 index = list(scope).index(decl)
925 if name
in [n2name(nodeN)
for nodeN
in decl.findall(
'.//{*}N')]:
930 stmtLst = [node
for node
in scope
if isExecutable(node)]
931 if len(stmtLst) == 0:
934 index = len(scope) - 1
937 index = list(scope).index(stmtLst[0])
941 ds.tail = scope[index - 1].tail
942 scope[index - 1].tail = previousTail
943 scope.insert(index, ds)
948 @updateTree('signal')
951 Add USE statements for module variables.
955 moduleVarList : list of list/tuple
956 List of module variable specifications. Each specification is a list
958 - Scope path (str): path to the location where USE should be added
959 (e.g., 'module:MOD/sub:SUB').
960 - Module name (str): name of the module to USE.
961 - Variable name(s) (str, list, or None):
962 - str: single variable name to import.
963 - list: list of variable names to import.
964 - None: add USE without ONLY clause (import all).
968 Import single variable Y from MODD_XX into subroutine FOO:
969 >>> pft = PYFT('input.F90')
970 >>> pft.addModuleVar([('sub:FOO', 'MODD_XX', 'Y')])
971 ! Adds: USE MODD_XX, ONLY: Y
973 Import multiple variables:
974 >>> pft.addModuleVar([('sub:FOO', 'MODD_XX', ['X', 'Y', 'Z'])])
975 ! Adds: USE MODD_XX, ONLY: X, Y, Z
977 Add USE without ONLY (import all):
978 >>> pft.addModuleVar([('sub:FOO', 'MODD_XX', None)])
981 Import into a module:
982 >>> pft.addModuleVar([('module:MOD', 'OTHER_MOD', 'VAR')])
986 - Existing USE statements for the same module are updated to include new variables.
987 - Duplicate imports are avoided (variables already imported are not re-added).
991 for (scopePath, moduleName, varName)
in moduleVarList:
994 elif not isinstance(varName, list):
996 scope = self.getScopeNode(scopePath)
999 useLst = [node
for node
in scope
if tag(node) ==
'use-stmt']
1004 usName = n2name(us.find(
'.//{*}module-N//{*}N'))
1005 usVar = [n2name(v.find(
'.//{*}N')).upper()
for v
in us.findall(
'.//{*}use-N')]
1006 if len(varName) == 0
and len(usVar) == 0
and usName.upper() == moduleName.upper():
1009 elif len(varName) > 0
and len(usVar) > 0
and usName.upper() == moduleName.upper():
1013 varName = [var
for var
in varName
if var.upper()
not in usVar]
1014 if len(varName) == 0:
1020 stmt = f
'USE {moduleName}'
1021 if len(varName) > 0:
1022 stmt +=
', ONLY:{}'.format(
', '.join(varName))
1023 us = createExpr(stmt)[0]
1026 if len(useLst) != 0:
1028 index = list(scope).index(useLst[-1]) + 1
1033 us.tail = scope[index - 1].tail
1034 scope[index - 1].tail =
'\n'
1035 scope.insert(index, us)
1036 scope.tree.signal(scope)
1041 Display unused variables on stdout.
1043 Searches through all scopes and displays variables that are declared
1044 but never used in the code.
1048 >>> pft = PYFT('input.F90')
1049 >>> pft.showUnusedVar()
1050 Some variables declared in /module:MOD/sub:SUB are unused:
1054 scopes = self.getScopes(excludeKinds=[
'type'])
1055 varUsed = self.
isVarUsed([(scope.path, v[
'n'])
1058 if v[
'scopePath'] == scope.path])
1059 for scope
in scopes:
1060 varList = [k[1].upper()
for (k, v)
in varUsed.items()
if (
not v)
and k[0] == scope.path]
1061 if len(varList) != 0:
1062 print(f
'Some variables declared in {scope.path} are unused:')
1063 print(
' - ' + (
'\n - '.join(varList)))
1068 Check for unused local variables in scopes.
1070 Issues a logging warning for each local variable that is declared but never used.
1071 When mustRaise is True, logs an error and raises PYFTError.
1075 mustRaise : bool, optional
1076 If False (default), issue warnings and continue.
1077 If True, issue errors and raise PYFTError.
1078 excludeList : list of str, optional
1079 List of variable names to exclude from the check.
1080 These variables will not trigger warnings even if unused.
1084 >>> pft = PYFT('input.F90')
1085 >>> pft.checkUnusedLocalVar() # Issues warnings
1086 WARNING: The LOCAL_VAR variable is not used...
1087 >>> pft.checkUnusedLocalVar(excludeList=['TEMP']) # Exclude TEMP
1088 >>> pft.checkUnusedLocalVar(mustRaise=True) # Raises error
1091 if excludeList
is None:
1094 excludeList = [v.upper()
for v
in excludeList]
1095 scopes = self.getScopes(excludeKinds=[
'type'])
1097 varUsed = self.
isVarUsed([(scope.path, v[
'n'])
1100 if (v[
'n'].upper()
not in excludeList
and
1102 v[
'scopePath'].split(
'/')[-1].split(
':')[0] !=
'module' and
1103 v[
'scopePath'] == scope.path)])
1105 for scope
in scopes:
1106 for var
in [k[1].upper()
for (k, v)
in varUsed.items()
1107 if (
not v)
and k[0] == scope.path]:
1108 message = f
"The {var} variable is not used in file " + \
1109 f
"'{scope.getFileName()}' for {scope.path}."
1112 logging.error(message)
1114 logging.warning(message)
1120 Remove unused local variables from declarations.
1122 Removes variables that are declared but never used in the code.
1123 Dummy arguments and module variables are not removed.
1127 excludeList : list of str, optional
1128 List of variable names to exclude from removal.
1129 These variables will be kept even if unused.
1130 simplify : bool, optional
1131 If True, also remove variables that become unused after removal
1132 (e.g., kind selectors).
1136 >>> pft = PYFT('input.F90')
1137 >>> pft.removeUnusedLocalVar() # Remove all unused locals
1139 Remove unused locals except TEMP and COUNTER:
1140 >>> pft.removeUnusedLocalVar(excludeList=['TEMP', 'COUNTER'])
1142 With simplification (remove cascading unused vars):
1143 >>> pft.removeUnusedLocalVar(simplify=True)
1147 - Only local variables (declared in SUBROUTINE/FUNCTION scope) are removed.
1148 - Dummy arguments (subroutine parameters) are preserved.
1149 - Variables imported via USE statements are preserved.
1151 if excludeList
is None:
1154 excludeList = [item.upper()
for item
in excludeList]
1156 allVar = [(scope.path, v[
'n'])
1157 for scope
in self.getScopes(excludeKinds=[
'type'])
1158 for v
in scope.varList
1159 if v[
'n'].upper()
not in excludeList
and v[
'scopePath'] == scope.path]
1160 self.
removeVarIfUnused(allVar, excludeDummy=
True, excludeModule=
True, simplify=simplify)
1165 Replace implicit array bounds with explicit bounds from declarations.
1167 Transforms array slice notation (e.g., A(:)) into explicit bounds
1168 based on the array's declaration.
1172 node : xml element, optional
1173 Specific XML node to transform. If None (default), transforms
1174 all implicit bounds in all scopes.
1178 Given declaration: REAL, DIMENSION(1:10) :: A, B
1179 And usage: A(:) = B(:)
1181 After transformation:
1186 - Only handles 1D array slices (A(:) notation).
1187 - Does not modify allocatable arrays (where ':' is part of declaration).
1188 - Does not modify character type arrays.
1191 nodes = [(scope, scope)
for scope
in self.getScopes()]
1193 nodes = [(self, node)]
1195 for (scope, childNode)
in nodes:
1196 for parent4
in [parent4
for parent4
1197 in childNode.findall(
'.//{*}section-subscript/../../../..')
1198 if parent4.find(
'./{*}R-LT/{*}component-R')
is None]:
1200 for parent
in parent4.findall(
'.//{*}section-subscript/..'):
1201 for sub
in parent.findall(
'.//{*}section-subscript'):
1202 lowerUsed = sub.find(
'./{*}lower-bound')
1203 upperUsed = sub.find(
'./{*}upper-bound')
1208 if lowerUsed
is None or \
1209 (lowerUsed.tail
is not None and ':' in lowerUsed.tail):
1210 if lowerUsed
is None or upperUsed
is None:
1212 varDesc = scope.varList.findVar(n2name(parent4.find(
'.//{*}N')))
1213 if varDesc
is not None and varDesc[
't']
is not None and \
1214 'CHAR' not in varDesc[
't']:
1215 lowerDecl, upperDecl = varDesc[
'as'][list(parent).index(sub)]
1216 if lowerDecl
is None:
1220 lowerBound = lowerDecl
if lowerUsed
is None \
1221 else alltext(lowerUsed)
1222 upperBound = upperDecl
if upperUsed
is None \
1223 else alltext(upperUsed)
1224 if upperBound
is not None:
1225 lowerXml, upperXml = createArrayBounds(lowerBound,
1231 if tag(nnn)
in (
'lower-bound',
'upper-bound'):
1235 "tag is {}".format(tag(nnn)))
1237 sub.extend([lowerXml, upperXml])
1243 Add explicit array parentheses to array variables.
1245 Transforms array variable references to include explicit slice notation.
1246 For example, A becomes A(:) when A is declared as an array.
1250 Given declaration: REAL, DIMENSION(10) :: A, B, C
1251 And usage: A = B + C
1253 After transformation:
1258 - Only modifies known arrays (declared with dimensions).
1259 - Excludes arrays in ALLOCATED, ASSOCIATED, and PRESENT intrinsic calls.
1260 - Does not modify pointer/allocatable arrays in call statements
1261 (to maintain interface compatibility).
1264 for scope
in self.getScopes():
1265 for node
in scope.iter():
1285 nodeToTransform =
None
1286 if tag(node)
in (
'allocate-stmt',
'deallocate-stmt',
'pointer-a-stmt',
1287 'T-decl-stmt',
'associate-stmt',
'function-stmt',
1288 'subroutine-stmt',
'interface-stmt',
'action-stmt',
1292 elif tag(node)
in (
'if-stmt',
'where-stmt',
'forall-stmt'):
1294 part = {
'if-stmt':
'condition-E',
'where-stmt':
'mask-E',
1295 'forall-stmt':
'forall-triplet-spec-LT'}[tag(node)]
1296 nodeToTransform = node.find(
'./{*}' + part)
1297 elif tag(node).endswith(
'-stmt'):
1298 nodeToTransform = node
1299 if nodeToTransform
is not None:
1300 scope.addArrayParenthesesInNode(nodeToTransform)
1305 Add explicit array parentheses to arrays within a specific XML node.
1310 XML node in which to add array parentheses.
1311 Only processes named-E elements within this node.
1315 addArrayParentheses : Add parentheses in all scopes.
1319 >>> pft = PYFT('input.F90')
1320 >>> node = pft.find('.//{*}a-stmt')
1321 >>> pft.addArrayParenthesesInNode(node)
1324 for namedE
in node.findall(
'.//{*}named-E'):
1325 if not namedE.find(
'./{*}R-LT'):
1326 if not self.isNodeInProcedure(namedE, (
'ALLOCATED',
'ASSOCIATED',
'PRESENT')):
1329 nodeN = namedE.find(
'./{*}N')
1330 var = self.
varList.findVar(n2name(nodeN))
1331 if var
is not None and var[
'as']
is not None and len(var[
'as']) > 0
and \
1332 not ((var[
'pointer']
or var[
'allocatable'])
and self.isNodeInCall(namedE)):
1338 nodeRLT = createElem(
'R-LT')
1339 namedE.insert(list(namedE).index(nodeN) + 1, nodeRLT)
1340 arrayR = createElem(
'array-R', text=
'(')
1341 nodeRLT.append(arrayR)
1342 sectionSubscriptLT = createElem(
'section-subscript-LT', tail=
')')
1343 arrayR.append(sectionSubscriptLT)
1345 sectionSubscript = createElem(
'section-subscript', text=
':', tail=
', ')
1346 sectionSubscriptLT.append(sectionSubscript)
1347 sectionSubscript.tail =
None
1352 Remove array parentheses if no index selection is needed.
1354 When an array has full slice notation (e.g., A(:,:)) that matches
1355 the entire array, removes the parentheses.
1360 XML node in which to remove unnecessary parentheses.
1364 Before: A(:,:) (when A is declared as 2D with full bounds)
1369 - Only removes parentheses when all dimensions use full slice (:) notation.
1370 - Preserves parentheses if any dimension has explicit index selection.
1371 - Excludes arrays in ALLOCATED, ASSOCIATED, and PRESENT intrinsic calls.
1374 for namedE
in node.findall(
'.//{*}named-E'):
1375 if namedE.find(
'./{*}R-LT'):
1376 if not self.isNodeInProcedure(namedE, (
'ALLOCATED',
'ASSOCIATED',
'PRESENT')):
1379 nodeN = namedE.find(
'./{*}N')
1380 var = self.
varList.findVar(n2name(nodeN))
1381 if var
is not None and var[
'as']
is not None and len(var[
'as']) > 0
and \
1382 not ((var[
'pointer']
or var[
'allocatable'])
and self.isNodeInCall(namedE)):
1383 arrayR = namedE.findall(
'./{*}R-LT')
1385 sectionSubscriptLT = arr.findall(
'.//{*}section-subscript-LT')
1386 for ss
in sectionSubscriptLT:
1387 lowerBound = ss.findall(
'.//{*}lower-bound')
1388 if len(lowerBound) == 0:
1390 par = self.getParent(ss, level=2)
1391 parOfpar = self.getParent(par)
1392 parOfpar.remove(par)
1398 Transform automatic arrays using customizable templates.
1400 Modifies automatic array declarations in subroutines and functions by
1401 applying templates for declaration, initialization, and cleanup.
1405 declTemplate : str, optional
1406 Template for the array declaration. If None, declaration is unchanged.
1407 startTemplate : str, optional
1408 Template for code to insert as the first executable statement
1410 endTemplate : str, optional
1411 Template for code to insert as the last executable statement
1412 (e.g., deallocation).
1417 Number of arrays modified.
1421 Each template can use the following placeholders (case-sensitive):
1423 ============ ==================================================
1424 Placeholder Description (for declaration "A(I, I:J, 0:I)")
1425 ============ ==================================================
1426 {name} Variable name (e.g., "A")
1427 {type} Type specification (e.g., "REAL")
1428 {doubledotshape} Colon-separated dimensions (e.g., ":, :, :")
1429 {shape} Bounds with indices (e.g., "I, I:J, 0:I")
1430 {lowUpList} Flattened bounds (e.g., "1, I, I, J, 0, I")
1431 ============ ==================================================
1435 Transform automatic arrays to allocatables:
1437 >>> pft = PYFT('input.F90')
1438 >>> pft.modifyAutomaticArrays(
1439 ... declTemplate="{type}, DIMENSION({doubledotshape}), ALLOCATABLE :: {name}",
1440 ... startTemplate="ALLOCATE({name}({shape}))",
1441 ... endTemplate="DEALLOCATE({name})"
1445 REAL, DIMENSION(I, I:J, 0:I) :: A
1449 REAL, DIMENSION(:,:,:), ALLOCATABLE :: A
1450 ALLOCATE(A(I, I:J, 0:I))
1456 - Only processes automatic arrays (stack-allocated based on parameters).
1457 - Excludes dummy arguments, allocatable, pointer, and function result arrays.
1458 - Arrays with initial values are not processed.
1459 - Variables are processed in dependency order to handle interdependencies.
1461 templates = {
'decl': declTemplate
if declTemplate
is not None else '',
1462 'start': startTemplate
if startTemplate
is not None else '',
1463 'end': endTemplate
if endTemplate
is not None else ''}
1466 for scope
in [scope
for scope
in self.getScopes()
1467 if scope.path.split(
'/')[-1].split(
':')[0]
in (
'sub',
'func')]:
1470 varListToTransform = []
1471 for var
in [var
for var
in scope.varList
1472 if var[
'as']
is not None and
1473 len(var[
'as']) > 0
and
1474 'CHARACTER' not in var[
't']
and
1475 not (var[
'arg']
or var[
'allocatable']
or
1476 var[
'pointer']
or var[
'result'])]:
1479 if var[
'init']
is None:
1481 varListToTransform.append(var)
1485 orderedVarListToTransform = []
1486 while len(varListToTransform) > 0:
1488 for var
in varListToTransform[:]:
1490 listN = [x
for dim
in var[
'asx']
for x
in dim
if x
is not None]
1491 listN = [n2name(nodeN).upper()
for asx
in listN
1492 for nodeN
in asx.findall(
'.//{*}N/{*}n/..')]
1493 if len(set(listN).intersection([v[
'n'].upper()
1494 for v
in varListToTransform])) == 0:
1496 varListToTransform.remove(var)
1497 orderedVarListToTransform.append(var)
1500 raise PYFTError(
'It seems that there is a circular reference in ' +
1501 'the declaration statements')
1503 for var
in orderedVarListToTransform[::-1]:
1506 templ = copy.deepcopy(templates)
1507 for templPart
in templ:
1508 if '{doubledotshape}' in templ[templPart]:
1509 templ[templPart] = templ[templPart].replace(
1510 '{doubledotshape}',
','.join([
':'] * len(var[
'as'])))
1511 if '{shape}' in templ[templPart]:
1513 for i
in range(len(var[
'as'])):
1514 if var[
'as'][i][0]
is None:
1515 result.append(var[
'as'][i][1])
1517 result.append(var[
'as'][i][0] +
':' + var[
'as'][i][1])
1518 templ[templPart] = templ[templPart].replace(
'{shape}',
', '.join(result))
1519 if '{name}' in templ[templPart]:
1520 templ[templPart] = templ[templPart].replace(
'{name}', var[
'n'])
1521 if '{type}' in templ[templPart]:
1522 templ[templPart] = templ[templPart].replace(
'{type}', var[
't'])
1523 if '{lowUpList}' in templ[templPart]:
1525 for i
in range(len(var[
'as'])):
1526 if var[
'as'][i][0]
is None:
1527 result.extend([1, var[
'as'][i][1]])
1529 result.extend([var[
'as'][i][0], var[
'as'][i][1]])
1530 templ[templPart] = templ[templPart].replace(
1531 '{lowUpList}',
', '.join([str(r)
for r
in result]))
1534 separator =
"!ABCDEFGHIJKLMNOPQRSTUVWabcdefghijklmnopqrstuvwxyz0123456789"
1536 for node
in createExpr(templ[
'decl'] +
'\n' + separator +
'\n' +
1537 templ[
'start'] +
'\n' + separator +
'\n' +
1539 templPart = list(templ.keys())[part]
1540 if not isinstance(templ[templPart], list):
1541 templ[templPart] = []
1542 if tag(node) ==
'C' and node.text == separator:
1545 templ[templPart].append(node)
1551 for decl
in scope.findall(
'./{*}T-decl-stmt'):
1552 index = list(scope).index(decl)
1553 if var[
'n']
in [n2name(nodeN)
for nodeN
in decl.findall(
'.//{*}N')]:
1555 scope.removeVar([(scope.path, var[
'n'])], simplify=
False)
1556 for nnn
in templ[
'decl'][::-1]:
1557 scope.insert(index, nnn)
1560 for nnn
in templ[
'start'][::-1]:
1561 scope.insertStatement(nnn,
True)
1562 for nnn
in templ[
'end'][::-1]:
1563 scope.insertStatement(nnn,
False)
1570 :param varSpec: a variable description, same form as the items return by self.varList
1571 :param implicitDeclaration: True if the variable may contain implicit declaration
1572 (e.g. outside of PHYEX)
1573 :return: the associated declarative statement
1575 if varSpec[
'use']
is not False:
1576 stmt = f
"USE {varSpec['use']}, ONLY: {varSpec['n']}"
1580 stmt +=
', DIMENSION('
1582 for el
in varSpec[
'as']:
1583 if el[0]
is None and el[1]
is None:
1588 dl.append(el[0] +
':' + el[1])
1589 if (varSpec[
'allocatable']
and not all(d ==
':' for d
in dl))
or \
1590 (any(d ==
':' for d
in dl)
and not varSpec[
'allocatable']):
1591 if not implicitDeclaration:
1592 raise PYFTError(
'Missing dim are mandatory and allowed ' +
1593 'only for allocatable arrays')
1594 stmt +=
', '.join(dl) +
')'
1595 if varSpec[
'allocatable']:
1596 stmt +=
", ALLOCATABLE"
1597 if varSpec[
'parameter']:
1598 stmt +=
", PARAMETER"
1599 if varSpec[
'i']
is not None:
1600 stmt +=
", INTENT(" + varSpec[
'i'] +
")"
1601 if varSpec[
'opt']
is True:
1602 stmt +=
", OPTIONAL"
1603 stmt +=
" :: " + varSpec[
'n']
1604 if varSpec[
'init']
is not None:
1605 stmt +=
"=" + varSpec[
'init']
1611 Find bounds and loop variable for a given array index
1612 :param arr: array node (named-E node with a array-R child)
1613 :param index: index of the rank of the array
1614 :param loopVar: None to create new variable for each added DO loop
1615 or a function that return the name of the variable to use for the
1617 This function returns a string (name of the variable), or True to create
1618 a new variable, or False to not transform this statement
1619 The functions takes as arguments:
1620 - lower and upper bounds as defined in the declaration statement
1621 - lower and upper bounds as given in the statement
1624 :return: the tuple (loopName, lowerBound, upperBound) where:
1625 loopName is the name of the variable to use for the loop
1626 lower and upper bounds are the bounds to use for the DO loop
1629 - False to discard this index
1630 - True to create a new variable to loop with
1632 name = n2name(arr.find(
'./{*}N'))
1633 ss = arr.findall(
'./{*}R-LT/{*}array-R/{*}section-subscript-LT/{*}section-subscript')[index]
1636 if ':' in alltext(ss):
1638 lowerUsed = ss.find(
'./{*}lower-bound')
1639 upperUsed = ss.find(
'./{*}upper-bound')
1640 varDesc = self.
varList.findVar(name, array=
True)
1641 if varDesc
is not None:
1642 lowerDecl, upperDecl = varDesc[
'as'][index]
1643 if lowerDecl
is None:
1646 lowerDecl, upperDecl =
None,
None
1652 varName = lowerDecl
is not None and upperDecl
is not None
1654 varName = loopVar(lowerDecl, upperDecl,
1655 None if lowerUsed
is None else alltext(lowerUsed),
1656 None if upperUsed
is None else alltext(upperUsed), name, index)
1658 lowerDecl
if lowerUsed
is None else alltext(lowerUsed),
1659 upperDecl
if upperUsed
is None else alltext(upperUsed))
1665 Transform a array-R into a parens-R node by replacing slices by variables
1666 In 'A(:)', the ':' is in a array-R node whereas in 'A(JL)', 'JL' is in a parens-R node.
1667 Both the array-R and the parens-R nodes are inside a R-LT node
1668 :param namedE: a named-E node
1669 :param table: dictionnary returned by the decode function
1670 :param varList: None or a VarList object in which varaibles are searched for
1693 nodeRLT = namedE.find(
'./{*}R-LT')
1694 arrayR = nodeRLT.find(
'./{*}array-R')
1695 if arrayR
is not None:
1696 index = list(nodeRLT).index(arrayR)
1697 parensR = createElem(
'parens-R', text=
'(', tail=
')')
1698 elementLT = createElem(
'element-LT')
1699 parensR.append(elementLT)
1701 for ss
in nodeRLT[index].findall(
'./{*}section-subscript-LT/{*}section-subscript'):
1702 element = createElem(
'element', tail=
', ')
1703 elementLT.append(element)
1704 if ':' in alltext(ss):
1706 varName = list(table.keys())[ivar]
1707 lower = ss.find(
'./{*}lower-bound')
1708 upper = ss.find(
'./{*}upper-bound')
1709 if lower
is not None:
1710 lower = alltext(lower)
1711 if upper
is not None:
1712 upper = alltext(upper)
1713 if lower
is not None and ss.text
is not None and ':' in ss.text:
1717 if lower
is None and upper
is None:
1721 element.append(createExprPart(varName))
1728 lower = self.
varList.findVar(n2name(namedE.find(
'{*}N')),
1729 array=
True)[
'as'][ivar][0]
1734 upper = self.
varList.findVar(n2name(namedE.find(
'{*}N')),
1735 array=
True)[
'as'][ivar][1]
1741 newlower = simplifyExpr(lower, add=varName, sub=table[varName][0])
1742 newupper = simplifyExpr(upper, add=varName, sub=table[varName][1])
1743 if newlower != newupper:
1744 raise PYFTError((
"Don't know how to do with an array declared with " +
1745 "'{la}:{ua}' and a loop from '{ll}' to '{ul}'"
1746 ).format(la=lower, ua=upper,
1747 ll=table[varName][0],
1748 ul=table[varName][1]))
1749 element.append(createExprPart(newlower))
1751 element.append(ss.find(
'./{*}lower-bound'))
1753 nodeRLT.remove(nodeRLT[index])
1754 nodeRLT.insert(index, parensR)
1759 Find bounds and loop variable given an array
1760 :param arr: array node (named-E node with a array-R child)
1761 :param loopVar: None to create new variable for each added DO loop
1762 or a function that return the name of the variable to use for the loop
1764 This function returns a string (name of the variable), or True to create
1765 a new variable, or False to not transform this statement
1766 The functions takes as arguments:
1767 - lower and upper bounds as defined in the declaration statement
1768 - lower and upper bounds as given in the statement
1771 :param extraVarList: None or list of variables (such as those contained in a VarList object)
1772 defined but not yet available in the self.varList object.
1773 :return: the tuple (table, newVar) where:
1774 table is a dictionnary: keys are loop variable names
1775 values are tuples with lower and upper bounds
1776 newVar is a list of loop variables not found in varList. This list has the same
1777 format as the varList list.
1779 In case the loop variable cannot be defined, the function returns (None, [])
1782 name = n2name(arr.find(
'./{*}N'))
1784 extraVarList = extraVarList
if extraVarList
is not None else []
1787 for iss, ss
in enumerate(arr.findall(
'./{*}R-LT/{*}array-R/{*}section-subscript-LT/' +
1788 '{*}section-subscript')):
1791 if ':' in alltext(ss):
1796 if varName
is not False and varName
in table:
1797 raise PYFTError((
"The variable {var} must be used for the rank #{i1} whereas " +
1798 "it is already used for rank #{i2} (for array {name})."
1799 ).format(var=varName, i1=str(iss),
1800 i2=str(list(table.keys()).index(varName)),
1811 varName =
'J' + str(j)
1812 var = self.
varList.findVar(varName, extraVarList=extraVarList + varNew)
1813 if (var
is None or var.get(
'new',
False))
and varName
not in table:
1815 varDesc = {
'as': [],
'asx': [],
'n': varName,
'i':
None,
1816 't':
'INTEGER',
'arg':
False,
'use':
False,
'opt':
False,
1817 'scopePath': self.
path}
1818 if varDesc
not in varNew:
1819 varNew.append(varDesc)
1821 elif (varName
is not False and
1822 self.
varList.findVar(varName, array=
False, exactScope=
True)
is None):
1824 varDesc = {
'as': [],
'asx': [],
'n': varName,
'i':
None,
1825 't':
'INTEGER',
'arg':
False,
'use':
False,
'opt':
False,
1826 'scopePath': self.
path}
1827 varNew.append(varDesc)
1830 table[varName] = (lower, upper)
1832 return (
None, [])
if False in table
else (table, varNew)
1838 :param oldName: old name of the variable
1839 :param newName: new name of the variable
1841 for node
in self.findall(
'.//{*}N'):
1842 if n2name(node).upper() == oldName.upper():
1844 for nnn
in node.findall(
'./{*}n')[1:]:
1847 node.find(
'./{*}n').text = newName
1852 :param varList: list of variables to remove if unused. Each item is a list or tuple of two
1854 The first one describes where the variable is used, the second one is
1855 the name of the variable. The first element is a '/'-separated path with
1856 each element having the form 'module:<name of the module>',
1857 'sub:<name of the subroutine>' or 'func:<name of the function>'
1858 :param excludeDummy: if True, dummy arguments are always kept untouched
1859 :param excludeModule: if True, module variables are always kept untouched
1860 :param simplify: try to simplify code (if we delete a declaration statement that used a
1861 variable as kind selector, and if this variable is not used else where,
1863 :return: the varList without the unremovable variables
1864 If possible, remove the variable from declaration, and from the argument list if needed
1868 varList = [v
for v
in varList
if v[0].split(
'/')[-1].split(
':')[0] !=
'module']
1870 varUsed = self.
isVarUsed(varList, dummyAreAlwaysUsed=excludeDummy)
1871 varListToRemove = []
1872 for scopePath, varName
in varList:
1873 assert scopePath.split(
'/')[-1].split(
':')[0] !=
'type', \
1874 "The removeVarIfUnused cannot be used with type members"
1875 if not varUsed[(scopePath, varName)]:
1876 varListToRemove.append([scopePath, varName])
1877 self.
removeVar(varListToRemove, simplify=simplify)
1878 return varListToRemove
1881 def isVarUsed(self, varList, exactScope=False, dummyAreAlwaysUsed=False):
1883 :param varList: list of variables to test. Each item is a list or tuple of two elements.
1884 The first one describes where the variable is declared, the second one is
1885 the name of the variable. The first element is a '/'-separated path with
1886 each element having the form 'module:<name of the module>',
1887 'sub:<name of the subroutine>' or 'func:<name of the function>'
1888 :param exactScope: True to search strictly in scope
1889 :param dummyAreAlwaysUsed: Returns True if variable is a dummy argument
1890 :return: a dict whose keys are the elements of varList, and values are True when the
1891 variable is used, False otherwise
1893 If exactScope is True, the function will search for variable usage
1894 only in this scope. But this feature has a limited interest.
1896 If exactScope is False:
1897 - if scopePath is a subroutine/function in a contains section,
1898 and if the variable is not declared in this scope, usages are
1899 searched in the module/subroutine/function upper that declared
1900 the variable and in all subroutines/functions in the contains section
1901 - if scopePath is a module/subroutine/function that has a
1902 contains sections, usages are searched in all subroutines/functions
1903 in the contains section
1905 To know if a variable can be removed, you must use exactScope=False
1909 allScopes = {scope.path: scope
for scope
in self.mainScope.getScopes()}
1913 locsVar = {(scopePath, varName): [scopePath]
1914 for scopePath, varName
in varList}
1917 for scopePath, varName
in varList:
1920 var = allScopes[scopePath].varList.findVar(varName)
1921 path = scopePath.split(
'/')[0]
if var
is None else var[
'scopePath']
1926 for scPath, sc
in allScopes.items():
1927 if scPath.startswith(path +
'/')
and \
1928 scPath.split(
'/')[-1].split(
':')[0] !=
'type':
1930 if sc.varList.findVar(varName, exactScope=
True)
is None:
1932 testScopes.append(scPath)
1933 locsVar[(scopePath, varName)] = testScopes
1937 for scopePath
in list(set(item
for sublist
in locsVar.values()
for item
in sublist)):
1938 usedVar[scopePath] = []
1940 for node
in allScopes[scopePath]:
1943 if not tag(node) ==
'use-stmt':
1944 if tag(node) ==
'T-decl-stmt':
1948 nodesN = node.findall(
'.//{*}_T-spec_//{*}N') + \
1949 node.findall(
'.//{*}shape-spec//{*}N')
1951 nodesN = node.findall(
'.//{*}N')
1954 for nodeN
in nodesN:
1955 if dummyAreAlwaysUsed:
1959 usedVar[scopePath].append(n2name(nodeN).upper())
1961 parPar = allScopes[scopePath].getParent(nodeN, 2)
1964 if parPar
is None or not tag(parPar) ==
'dummy-arg-LT':
1965 usedVar[scopePath].append(n2name(nodeN).upper())
1968 for scopePath, varName
in varList:
1969 assert scopePath.split(
'/')[-1].split(
':')[0] !=
'type', \
1970 'We cannot check type component usage'
1971 result[(scopePath, varName)] = any(varName.upper()
in usedVar[scopePath]
1972 for scopePath
in locsVar[(scopePath, varName)])
1978 @updateTree('signal')
1979 def addONLY(self, parserOptions=None, wrapH=False):
1981 Adds missing ONLY clause to USE statements
1982 :param parserOptions, wrapH: see the PYFT class
1984 for useStmt
in self.findall(
'.//{*}use-stmt'):
1985 module = useStmt.find(
'.//{*}module-N')
1986 if module.tail
is None or module.tail.replace(
' ',
'').upper() !=
',ONLY:':
1988 modulename = n2name(module.find(
'.//{*}N')).upper()
1989 modulescope =
'module:' + modulename
1990 modulefile = self.tree.scopeToFiles(modulescope)
1991 if len(modulefile) != 1:
1992 raise PYFTError(f
'No or several files define the {modulename} module')
1997 if self.getFileName() == os.path.normpath(modulefile[0]):
1999 xml = self.mainScope
2003 modulefile[0], parserOptions, wrapH, tree=self.tree,
2004 clsPYFT=self._mainScope.__class__)
2008 symbols.extend(v[
'n']
for v
in xml.varList.restrict(modulescope,
True))
2010 for scope
in xml.getScopeNode(modulescope,
2011 excludeContains=
False).getScopes(
2013 excludeContains=
False,
2014 includeItself=
False):
2015 if scope.path.split(
'/')[1].split(
':')[0] ==
'interface':
2016 if scope.path.split(
'/')[1].split(
':')[1] !=
'--UNKNOWN--':
2018 symbols.append(scope.path.split(
'/')[1].split(
':')[1])
2019 if len(scope.path.split(
'/')) == 3:
2021 symbols.append(scope.path.split(
'/')[2].split(
':')[1])
2023 symbols.append(scope.path.split(
'/')[1].split(
':')[1])
2024 symbols = sorted(set(symbols))
2031 module.tail =
', ONLY: '
2032 renameLT = createElem(
'rename-LT')
2033 useStmt.append(renameLT)
2035 parent = self.getScopePath(useStmt)
2036 isVarUsed = self.
isVarUsed([(parent, symbol.upper())
for symbol
in symbols])
2037 for symbol
in symbols:
2038 if isVarUsed[(parent, symbol.upper())]:
2039 useN = createElem(
'use-N', childs=createElem(
'N',
2040 childs=createElem(
'n', text=symbol)))
2041 rename = createElem(
'rename', tail=
', ', childs=useN)
2042 renameLT.append(rename)
2045 previous = self.getSiblings(useStmt, before=
True, after=
False)
2046 previous =
None if len(previous) == 0
else previous[-1]
2047 if previous
is not None and useStmt.tail
is not None:
2048 if previous.tail
is None:
2050 previous.tail += useStmt.tail
2051 self.getParent(useStmt).remove(useStmt)
2052 self.tree.signal(self)
2062 def addArgInTree(self, varName, declStmt, pos, stopScopes, moduleVarList=None,
2064 parserOptions=None, wrapH=False):
2066 Adds an argument to the routine and propagates it upward until we encounter a scope
2067 where the variable exists or a scope in stopScopes
2068 :param varName: variable name
2069 :param declStmt: declarative statment (will be used by addVar)
2070 :param pos: position of the variable in the list of dummy argument
2071 :param stopScopes: list of scopes to reach
2072 :param moduleVarList: list of module variable specification to insert in the xml code
2073 a module variable specification is a list of two elements:
2075 - variable name or or list of variable names
2076 or None to add a USE statement without the ONLY attribute
2077 use moduleVarList to not add module variables
2078 :param otherNames: None or list of other variable names that can be used
2079 These variables are used first
2080 :param parserOptions, wrapH: see the PYFT class
2082 Argument is inserted only on paths leading to one of scopes listed in stopScopes
2084 def insertInArgList(varName, varNameToUse, pos, callFuncStmt):
2086 Insert varName in the list of arguments to the subroutine or function call
2087 :param varName: name of the dummy argument
2088 :param varNameToUse: name of the variable
2089 :param pos: inclusion position
2090 :param callFuncStmt: call statement or function call
2092 argList = callFuncStmt.find(
'./{*}R-LT/{*}parens-R/{*}element-LT')
2093 if argList
is not None:
2094 container = createElem(
'element')
2096 argList = callFuncStmt.find(
'./{*}arg-spec')
2097 container = createElem(
'arg')
2100 callFuncStmt.find(
'./{*}procedure-designator').tail =
'('
2101 argList = createElem(
'arg-spec', tail=
')')
2102 callFuncStmt.append(argList)
2103 item = createExprPart(varNameToUse)
2104 previous = pos - 1
if pos >= 0
else len(argList) + pos
2105 while previous >= 0
and tag(argList[previous])
in (
'C',
'cnt'):
2107 following = pos
if pos > 0
else len(argList) + pos + 1
2108 while following <= len(argList) - 1
and tag(argList[following])
in (
'C',
'cnt'):
2110 if (previous >= 0
and argList[previous].find(
'./{*}arg-N/{*}k')
is not None)
or \
2111 (following <= len(argList) - 1
and
2112 argList[following].find(
'./{*}arg-N/{*}k')
is not None)
or \
2113 following == len(argList):
2119 k = createElem(
'k', text=varName)
2120 argN = createElem(
'arg-N', tail=
'=')
2122 argN.set(
'n', varName)
2123 container.append(argN)
2124 container.append(item)
2125 self.insertInList(pos, container, argList)
2127 if self.
path in stopScopes
or self.tree.isUnderStopScopes(self.
path, stopScopes):
2130 var = self.
varList.findVar(varName, exactScope=
True)
2131 if otherNames
is not None:
2132 vOther = [self.
varList.findVar(v, exactScope=
True)
for v
in otherNames]
2133 vOther = [v
for v
in vOther
if v
is not None]
2139 self.
addVar([[self.
path, varName, declStmt, pos]])
2140 if moduleVarList
is not None:
2143 for (moduleName, moduleVarNames)
in moduleVarList])
2146 if len(self.
path.split(
'/')) == 1:
2147 filename, scopePathInterface = self.tree.findScopeInterface(self.
path)
2148 if filename
is not None:
2151 if self.getFileName() == os.path.normpath(filename):
2153 xml = self.mainScope
2157 filename, parserOptions, wrapH, tree=self.tree,
2158 clsPYFT=self._mainScope.__class__)
2160 scopeInterface = xml.getScopeNode(scopePathInterface)
2161 varInterface = scopeInterface.varList.findVar(varName, exactScope=
True)
2162 if varInterface
is None:
2163 scopeInterface.addVar([[scopePathInterface, varName,
2165 if moduleVarList
is not None:
2168 [(scopePathInterface, moduleName, moduleVarNames)
2169 for (moduleName, moduleVarNames)
in moduleVarList])
2176 if var
is None and self.
path not in stopScopes:
2179 for scopePathUp
in self.tree.calledByScope(self.
path):
2180 if scopePathUp
in stopScopes
or self.tree.isUnderStopScopes(scopePathUp,
2185 for filename
in self.tree.scopeToFiles(scopePathUp):
2188 if self.getFileName() == os.path.normpath(filename):
2190 xml = self.mainScope
2194 filename, parserOptions, wrapH,
2196 clsPYFT=self._mainScope.__class__)
2198 scopeUp = xml.getScopeNode(scopePathUp)
2200 scopeUp.addArgInTree(
2201 varName, declStmt, pos,
2202 stopScopes, moduleVarList, otherNames,
2203 parserOptions=parserOptions,
2206 name = self.
path.split(
'/')[-1].split(
':')[1].upper()
2208 varNameToUse = varName
2209 if otherNames
is not None:
2210 vOther = [scopeUp.varList.findVar(v, exactScope=
True)
2211 for v
in otherNames]
2212 vOther = [v
for v
in vOther
if v
is not None]
2214 varNameToUse = vOther[-1][
'n']
2215 if self.
path.split(
'/')[-1].split(
':')[0] ==
'sub':
2217 for callStmt
in scopeUp.findall(
'.//{*}call-stmt'):
2218 callName = n2name(callStmt.find(
2219 './{*}procedure-designator/{*}named-E/{*}N')).upper()
2220 if callName == name:
2221 insertInArgList(varName, varNameToUse, pos, callStmt)
2225 for funcCall
in scopeUp.findall(
2226 './/{*}named-E/{*}R-LT/{*}parens-R/' +
2227 '{*}element-LT/../../..'):
2228 funcName = n2name(funcCall.find(
'./{*}N')).upper()
2229 if funcName == name:
2230 insertInArgList(varName, varNameToUse, pos, funcCall)
2241 for interface
in self.findall(
'.//{*}interface-construct/{*}' +
2242 'program-unit/{*}subroutine-stmt/' +
2243 '{*}subroutine-N/{*}N/../../../'):
2244 if n2name(interface.find(
'./{*}subroutine-stmt/' +
2245 '{*}subroutine-N/' +
2246 '{*}N')).upper() == name:
2248 raise PYFTError(
'This case is not yet implemented')