4This module implements the scope stuff
23 This class acts as a view of an ElementTree exposing only a part of the subelements
26 def __init__(self, xml, excludeContains=False):
28 :param xml: xml corresponding to a scope
29 :param excludeContains: do not take into account the CONTAINS part
38 :param xml: xml corresponding to a scope
39 :return: a node (possibly the xml node) containing only the relevant subelements
42 contains = self.
_xml.
find(
'./{*}contains-stmt')
45 indexContains = list(self.
_xml).index(contains)
46 childNode = createElem(
'virtual')
47 childNode.extend(self.
_xml[:indexContains] + [self.
_xml[-1]])
56 https://docs.python.org/3/library/xml.etree.elementtree.html#xml.etree.ElementTree.Element.tag
63 https://docs.python.org/3/library/xml.etree.elementtree.html#xml.etree.ElementTree.Element.tail
70 https://docs.python.org/3/library/xml.etree.elementtree.html#xml.etree.ElementTree.Element.text
78 https://docs.python.org/3/library/xml.etree.elementtree.html#xml.etree.ElementTree.Element.findtext
84 https://docs.python.org/3/library/xml.etree.elementtree.html#xml.etree.ElementTree.Element.iterfind
90 https://docs.python.org/3/library/xml.etree.elementtree.html#xml.etree.ElementTree.Element.itertext
94 def __getitem__(self, *args, **kwargs):
97 def __len__(self, *args, **kwargs):
103 def find(self, *args, **kwargs):
105 https://docs.python.org/3/library/xml.etree.elementtree.html#xml.etree.ElementTree.Element.find
111 https://docs.python.org/3/library/xml.etree.elementtree.html#xml.etree.ElementTree.Element.findall
115 def iter(self, *args, **kwargs):
117 https://docs.python.org/3/library/xml.etree.elementtree.html#xml.etree.ElementTree.Element.iter
123 https://docs.python.org/3/library/xml.etree.elementtree.html#xml.etree.ElementTree.Element.items
132 https://docs.python.org/3/library/xml.etree.elementtree.html#xml.etree.ElementTree.Element.clear
141 https://docs.python.org/3/library/xml.etree.elementtree.html#xml.etree.ElementTree.Element.append
148 https://docs.python.org/3/library/xml.etree.elementtree.html#xml.etree.ElementTree.Element.extend
155 :param index: index in the virtual node
156 :return: index in the _xml node
160 contains = self.
_xml.
find(
'./{*}contains-stmt')
163 indexContains = list(self.
_xml).index(contains)
165 if index > indexContains
or index < -indexContains - 1:
166 raise IndexError(
'list index out of range')
172 index = indexContains + index + 1
174 return len(self.
_xml)
if index == indexContains
else index
176 def __setitem__(self, index, item):
180 def __delitem__(self, index):
185 https://docs.python.org/3/library/xml.etree.elementtree.html#xml.etree.ElementTree.Element.insert
192 Remove node from the xml
194 if isinstance(node, ElementView):
196 self.getParent(node).
remove(node)
201 This class wrapps the xml node representing a FORTRAN scope
203 SCOPE_STMT = {
'module':
'module-stmt',
204 'func':
'function-stmt',
205 'sub':
'subroutine-stmt',
207 'prog':
'program-stmt',
208 'interface':
'interface-stmt'}
209 SCOPE_CONSTRUCT = {
'module':
'program-unit',
210 'func':
'program-unit',
211 'sub':
'program-unit',
212 'type':
'T-construct',
213 'prog':
'program-unit',
214 'interface':
'interface-construct'}
216 def __init__(self, xml, scopePath='/', parentScope=None,
217 enableCache=False, tree=None, excludeContains=False):
219 :param xml: xml corresponding to this PYFTscope
220 :param scopePath: scope path ('/' separated string) of this node
221 :param parentScope: parent PYFTscope instance
222 :param enableCache: True to cache node parents
223 :param tree: an optional Tree instance
224 :param excludeContains: do not take into account the CONTAINS part
226 super().
__init__(xml=xml, excludeContains=excludeContains)
227 self.
_mainScope = self
if parentScope
is None else parentScope._mainScope
228 self.
_path = scopePath
230 self.
tree =
Tree()
if tree
is None else tree
233 if enableCache
and parentScope
is None:
235 for node
in self.
iter():
241 result = cls.__new__(cls)
242 result.__dict__.update(self.
__dict__)
245 def __deepcopy__(self, memo):
247 result = cls.__new__(cls)
248 memo[id(self)] = result
250 setattr(result, key, copy.deepcopy(val, memo))
255 Get some attributes defined in the PYFT class
257 if attr
in (
'SHARED_TREE',
'NO_PARALLEL_LOCK',
'PARALLEL_FILE_LOCKS '):
259 raise AttributeError(f
"{attr} doesn't exist")
264 Return the current path
271 Return the main scope (the upper scope in the file)
278 Return the parent of the current scope
285 :param item: item whose parent is to be searched
286 :param level: number of degrees (1 to get the parent, 2 to get
287 the parent of the parent...)
293 return node
in self.
mainScope._cacheParent.get(id(node), [])
and \
295 check(self.
mainScope._cacheParent[id(node)]))
300 parent = self.
mainScope._cacheParent[id(item)]
303 if item
in list(node):
307 self.
mainScope._cacheParent[id(item)] = node
309 return parent
if level == 1
else self.
getParent(parent, level - 1)
313 :param item: item whose siblings are to be searched
314 :param before: returns siblings before
315 :param after: returns siblings after
316 By default before and after are True so that all siblings are returned
321 siblings = siblings[:siblings.index(item)]
323 siblings = siblings[siblings.index(item) + 1:]
324 return [s
for s
in siblings
if s != item]
330 Internal methode to compute the name of a scope
331 :param node: program-unit node
336 nodeN = node.find(
'.//{*}N')
337 if nodeN
is not None and nodeN.find(
'.//{*}N')
is not None:
339 nodeN = nodeN.find(
'.//{*}N')
340 if nodeN
is not None:
341 name = n2name(nodeN).upper()
349 Internal methode to compute a path part from a node
350 :param node: program-unit node
351 :return: path part (e.g. module:MODU)
361 Method to normalize a scope path
363 return '/'.join([(k.lower() +
':' + w.upper())
364 for (k, w)
in [component.split(
':')
365 for component
in scopePath.split(
'/')]])
370 Shows the list of scopes found in the source code
371 :param includeItself: include itself if self represent a "valid" scope (not a file)
373 print(
"These scopes have been found in the source code:")
374 print(
"\n".join([
' - ' + scope.path
375 for scope
in self.
getScopes(includeItself=includeItself)]))
378 def getScopes(self, level=-1, excludeContains=True, excludeKinds=None, includeItself=True):
380 :param level: -1 to get all child scopes
381 1 to get only direct child scopes
382 2 to get direct and direct of direct ...
383 :param excludeContains: if True, each PYFTscope which is a module, function or subroutine
384 that contain (after a 'CONTAINS' statement) other subroutines or
385 functions, those contained subroutines or functions are excluded
386 from the result; but the PYFTscope contains the 'END' statement
387 of the module/subroutine or function.
388 :param excludeKinds: if not None, is a list of scope kinds to exclude
389 :param includeItself: include itself if self represent a "valid" scope (not a file)
390 :return: list of PYFTscope found in the current scope
392 assert level == -1
or level > 0,
'level must be -1 or a positive int'
394 def _getRecur(node, level, basePath=''):
396 if tag(node) ==
'object':
397 usenode = node.find(
'./{*}file')
401 for child
in [child
for child
in usenode
404 if excludeKinds
is None or nodePath.split(
':')[0]
not in excludeKinds:
405 scopePath = nodePath
if basePath
in (
'',
'/') \
406 else basePath +
'/' + nodePath
408 scopePath=scopePath, parentScope=self,
409 enableCache=
False, tree=self.
tree,
410 excludeContains=excludeContains))
412 results.extend(_getRecur(child, level - 1, scopePath))
420 return _getRecur(self, level, self.
pathpathpath) + itself
423 def getScopeNode(self, scopePath, excludeContains=True, includeItself=True):
425 :param scopePath: scope path to search for
426 :param excludeContains: see getScopes
427 :param includeItself: include itself if self represent a "valid" scope (not a file)
428 :return: PYFTscope whose path is the path asked for
430 scope = [scope
for scope
in self.
getScopes(excludeContains=excludeContains,
431 includeItself=includeItself)
432 if scope.path == scopePath]
434 raise PYFTError(f
'{scopePath} not found')
436 raise PYFTError(f
'{scopePath} found several times')
442 :param node: node to test
443 :return: True if node is a scope node (construct node around a
444 module, subroutine, function or type declaration)
451 :param item: item whose scope parent is to be searched
452 :param mustRaise: True to raise an exception if parent is not found
453 :return: the scope parent node of item
454 Example: if item is a call statement, result is the program-unit node
455 in which the call statement is
458 while result
is not None and not self.
isScopeNode(result):
460 if result
is None and mustRaise:
461 raise PYFTError(
"The scope parent has not been found.")
467 :param item: item whose path must be determined
468 :param includeItself: include the item if it is a scope node
469 :return: the full path of the structure containing item
476 while item
is not None:
479 return '/'.join(result)
483 :return: the name of the input file name or 'unknown' if not available
484 in the xml fragment provided
486 return os.path.normpath(self.
mainScope.
find(
'.//{*}file').attrib[
'name'])
490 def empty(self, addStmt=None, simplify=False):
492 Empties the scope by removing all statements except dummy arguments declaration
493 and USE statements (because they can be useful for the dummy argument declarations).
494 :param addStmt: add this statement in the body of the emptied scopes
495 :param simplify: try to simplify code
498 for scope
in self.
getScopes(level=1, excludeContains=
False):
499 if scope.path.split(
'/')[-1].split(
':')[0] ==
'module':
500 scopes.extend(scope.getScopes(level=1, excludeContains=
False, includeItself=
False))
503 tagExcluded = (list(self.
SCOPE_STMT.values()) +
504 [
'end-' + decl
for decl
in self.
SCOPE_STMT.values()] +
505 [
'T-decl-stmt',
'use-stmt',
'C'])
507 for node
in list(scope):
508 if tag(node)
not in tagExcluded:
510 scope.removeUnusedLocalVar(simplify=simplify)
511 if addStmt
is not None:
512 if isinstance(addStmt, str):
513 addStmt = createExpr(addStmt)
514 elif not isinstance(addStmt, list):
517 scope.insertStatement(stmt,
False)