36 The fparser2 symbol-table module. Defines various classes as well as 37 the single, global SYMBOL_TABLES instance. The latter is a container 38 for all of the top-level scoping units encountered during parsing. 41 from collections
import namedtuple
45 """Base class exception for symbol-table related errors.""" 50 Class encapsulating functionality for the global symbol-tables object. 51 This is a container for all symbol tables constructed while parsing 52 code. All names are converted to lower case (since Fortran is not 58 self._symbol_tables = {}
60 self._scoping_unit_classes = []
62 self._current_scope =
None 65 self._enable_checks =
False 69 f
"SymbolTables: {len(self._symbol_tables)} tables\n" 70 "========================\n" 72 return result +
"\n".join(sorted(self._symbol_tables.keys()))
76 Sets whether or not to enable consistency checks in every symbol 77 table that is created during a parse. 79 :param bool value: whether or not checks are enabled. 86 Deletes any stored SymbolTables but retains the stored list of 87 classes that define scoping units. 95 Add a new symbol table with the supplied name. The name will be 96 converted to lower case if necessary. 98 :param str name: the name for the new table. 100 :returns: the new symbol table. 101 :rtype: :py:class:`fparser.two.symbol_table.SymbolTable` 103 :raises SymbolTableError: if there is already an entry with the \ 106 lower_name = name.lower()
109 f
"The table of top-level (un-nested) symbol tables already " 110 f
"contains an entry for '{lower_name}'" 118 Find the named symbol table and return it. 120 :param str name: the name of the required symbol table (not case \ 123 :returns: the named symbol table. 124 :rtype: :py:class:`fparser.two.symbol_table.SymbolTable` 132 :returns: the fparser2 classes that are taken to mark the start of \ 133 a new scoping region. 134 :rtype: list of types 139 @scoping_unit_classes.setter
142 Set the list of fparser2 classes that are taken to mark the start of \ 143 a new scoping region. 145 :param value: the list of fparser2 classes. 146 :type value: list of types 148 :raises TypeError: if the supplied value is not a list of types. 151 if not isinstance(value, list):
153 f
"Supplied value must be a list but got '{type(value).__name__}'" 155 if not all(isinstance(item, type)
for item
in value):
156 raise TypeError(f
"Supplied list must contain only classes but got: {value}")
162 :returns: the symbol table for the current scoping unit or None. 163 :rtype: :py:class:`fparser.two.symbol_table.SymbolTable` or NoneType 169 Called when the parser enters a new scoping region (i.e. when it 170 encounters one of the classes listed in `_scoping_unit_classes`). 171 Sets the 'current scope' to be the symbol table with the supplied name. 172 If we are not currently within a tree of scoping regions then a 173 new entry is created in the internal dict of symbol tables. If there 174 is an existing tree then a new table is created and added to the 177 :param str name: name of the scoping region. 185 table = self.
lookup(lname)
188 table = self.
add(lname)
202 Marks the end of the processing of the current scoping unit. Since 203 we are exiting the current scoping region, the new 'current scoping 204 region' will be its parent. 206 :raises SymbolTableError: if there is no current scope from which to \ 215 Removes the named symbol table and any descendants it may have. 216 When searching for the named table, the current scope takes priority 217 followed by the list of top-level symbol tables. 219 :param str name: the name of the symbol table to remove (not case \ 221 :raises SymbolTableError: if the named symbol table is not in the \ 222 current scope or in the list of top-level symbol tables. 236 msg = f
"Failed to find a table named '{name}' in " 239 f
"either the current scope (which contains " 240 f
"{[child.name for child in self._current_scope.children]}) or " 243 f
"the list of top-level symbol tables " 244 f
"({list(self._symbol_tables.keys())})." 254 f
"Cannot remove top-level symbol table '{name}' because the " 255 f
"current scope '{self._current_scope.name}' has it as an ancestor." 263 Class capturing information on all USE statements referring to a given 266 A USE statement can rename an imported symbol so as to avoid a clash in 267 the local scope, e.g. `USE my_mod, alocal => amod` where `amod` is the 268 name of the symbol declared in `my_mod`. This renaming can also occur 269 inside an Only_List, e.g. `USE my_mod, only: alocal => amod`. 271 :param str name: the name of the module. 272 :param only_list: list of 2-tuples giving the (local-name, module-name) \ 273 of symbols that appear in an Only_List. If a symbol is not re-named \ 274 then module-name can be None. 275 :type only_list: Optional[List[Tuple[str, str | NoneType]]] 276 :param rename_list: list of 2-tuples given the (local-name, module-name) \ 277 of symbols that appear in a Rename_List. 278 :type rename_list: Optional[List[Tuple[str, str]]] 280 :raises TypeError: if any of the supplied parameters are of the wrong type. 284 def __init__(self, name, only_list=None, rename_list=None):
285 if not isinstance(name, str):
287 f
"The name of the module must be a str but got " 288 f
"'{type(name).__name__}'" 293 if only_list
and not all(
294 isinstance(item[0], str)
and (item[1]
is None or isinstance(item[1], str))
295 for item
in only_list
298 f
"If present, the only_list must be a list of " 299 f
"2-tuples of (str, str | NoneType) but got: {only_list}" 302 if rename_list
and not all(
303 isinstance(item[0], str)
and isinstance(item[1], str)
304 for item
in rename_list
307 f
"If present, the rename_list must be a list of " 308 f
"2-tuples of (str, str) but got: {rename_list}" 311 self.
_name = name.lower()
319 if only_list
is not None:
322 self.
_only_set = set(local_name.lower()
for local_name, _
in only_list)
329 self.
_rename_set = set(local_name.lower()
for local_name, _
in rename_list)
334 def _validate_tuple_list(name, tlist):
336 Validate the supplied list of tuples. 338 :param str name: the name of the list being validated. 339 :param tlist: the list of tuples to validate. 340 :type tlist: Optional[List[Tuple[str, str | NoneType]]] 342 :raises TypeError: if the supplied list is of the wrong type. 349 if not isinstance(tlist, list):
351 f
"If present, the {name}_list must be a list but " 352 f
"got '{type(tlist).__name__}'" 354 if not all(isinstance(item, tuple)
and len(item) == 2
for item
in tlist):
356 f
"If present, the {name}_list must be a list of " 357 f
"2-tuples but got: {tlist}" 360 def _store_symbols(self, rename_list):
362 Utility that updates the local list of symbols and renaming map for 363 the given list of local-name, module-name tuples. If a symbol is 364 not renamed then module-name can be None. 366 :param rename_list: list of local-name, module-name pairs. 367 :type rename_list: List[Tuple[str, str | NoneType]] 370 for local_name, orig_name
in rename_list:
371 lname = local_name.lower()
372 oname = orig_name.lower()
if orig_name
else None 374 self.
_symbols[lname] = SymbolTable.Symbol(lname,
"unknown")
380 Update the current information with the information on the USE held 383 :param other: the other Use instance from which to update this one. 384 :type other: :py:class:`fparser.two.symbol_table.Use` 386 :raises TypeError: if 'other' is of the wrong type. 387 :raises ValueError: if 'other' is for a module with a different \ 388 name to the current one. 391 if not isinstance(other, ModuleUse):
393 f
"update() must be supplied with an instance of " 394 f
"ModuleUse but got '{type(other).__name__}'" 397 if self.
name != other.name:
399 f
"The ModuleUse supplied to update() is for module " 400 f
"'{other.name}' but this ModuleUse is for module " 407 for local_name
in other.only_list:
409 self.
_symbols[local_name] = SymbolTable.Symbol(
410 local_name,
"unknown" 419 if other.rename_list:
420 for local_name
in other.rename_list:
422 self.
_symbols[local_name] = SymbolTable.Symbol(
423 local_name,
"unknown" 441 :returns: the name of the Fortran module. 449 :returns: the names of all symbols associated with USE(s) of this \ 458 :returns: the local names that appear in an Only_List or None if there \ 460 :rtype: Optional[List[str]] 469 :returns: the local names that appear in a Rename_List or None if there \ 471 :rtype: Optional[List[str]] 480 :returns: whether there is a wildcard import from this module. 487 :returns: the name of the supplied symbol as declared in the module. 495 Class implementing a single symbol table. 497 Since this functionality is not yet fully mature, checks that new symbols 498 don't clash with existing symbols are disabled by default. 499 Once #201 is complete it is planned to switch this so that the checks 500 are instead enabled by default. 502 :param str name: the name of this scope. Will be the name of the \ 503 associated module or routine. 504 :param parent: the symbol table within which this one is nested (if any). 505 :type parent: :py:class:`fparser.two.symbol_table.SymbolTable.Symbol` 506 :param bool checking_enabled: whether or not validity checks are \ 507 performed for symbols added to the table. 514 Symbol = namedtuple(
"Symbol",
"name primitive_type")
516 def __init__(self, name, parent=None, checking_enabled=False):
517 self.
_name = name.lower()
533 header =
"===========\n" 534 symbols =
"Symbols:\n" 537 uses =
"Used modules:\n" 539 uses +=
"\n".join(list(self.
_modules.keys())) +
"\n" 540 return f
"{header}Symbol Table '{self._name}'\n{symbols}{uses}{header}" 544 Creates a new Symbol with the specified properties and adds it to 545 the symbol table. The supplied name is converted to lower case. 547 TODO #201 add support for other symbol properties (kind, shape 550 :param str name: the name of the symbol. 551 :param str primitive_type: the primitive type of the symbol. 553 :raises TypeError: if any of the supplied parameters are of the \ 555 :raises SymbolTableError: if the symbol table already contains an 556 entry with the supplied name. 558 if not isinstance(name, str):
560 f
"The name of the symbol must be a str but got " 561 f
"'{type(name).__name__}'" 564 if not isinstance(primitive_type, str):
566 f
"The primitive type of the symbol must be specified as a str " 567 f
"but got '{type(primitive_type).__name__}'" 574 f
"Symbol table already contains a symbol for" 575 f
" a variable with name '{name}'" 579 f
"Symbol table already contains a use of a " 580 f
"module with name '{name}'" 582 for mod_name, mod
in self.
_modules.items():
583 if mod.symbol_names
and lname
in mod.symbol_names:
585 f
"Symbol table already contains a use of a symbol " 586 f
"named '{name}' from module '{mod_name}'" 589 self.
_data_symbols[lname] = SymbolTable.Symbol(lname, primitive_type.lower())
593 Creates an entry in the table for the USE of a module with the supplied 594 name. If no `only_list` is supplied then this USE represents a wildcard 595 import of all public symbols in the named module. If the USE statement 596 has an ONLY clause but without any named symbols then `only_list` 597 should be an empty list. 599 A USE can also have one or more rename entries *without* an only list. 601 :param str name: the name of the module being imported via a USE. Not \ 603 :param only_list: if there is an 'only:' clause on the USE statement \ 604 then this contains a list of tuples, each holding the local name \ 605 of the symbol and its name in the module from which it is \ 606 imported. These names are case insensitive. 607 :type only_list: Optional[List[Tuple[str, str | NoneType]]] 608 :param rename_list: a list of symbols that are renamed from the scope \ 609 being imported. Each entry is a tuple containing the name in the \ 610 local scope and the corresponding name in the module from which it\ 611 is imported. These names are case insensitive. 612 :type rename_list: Optional[List[Tuple[str, str]]] 615 use =
ModuleUse(name, only_list, rename_list)
626 Lookup the symbol with the supplied name. 628 :param str name: the name of the symbol to lookup (not case sensitive). 630 :returns: the named symbol. 631 :rtype: :py:class:`fparser.two.symbol_table.SymbolTable.Symbol` 633 :raises KeyError: if the named symbol cannot be found in this or any \ 643 raise KeyError(f
"Failed to find symbol named '{lname}'")
648 :returns: the name of this symbol table (scoping region). 656 :returns: the parent symbol table (scoping region) that contains \ 658 :rtype: :py:class:`fparser.two.symbol_table.SymbolTable` or NoneType 663 def parent(self, value):
665 Set the parent scope for this symbol table. 667 :param value: the parent symbol table. 668 :type value: :py:class:`fparser.two.symbol_table.SymbolTable` 670 :raises TypeError: if the supplied value is not None or a SymbolTable. 673 if value
is not None and not isinstance(value, SymbolTable):
675 f
"Unless it is None, the parent of a SymbolTable must also be " 676 f
"a SymbolTable but got '{type(value).__name__}'" 682 Adds a child symbol table (scoping region nested within this one). 684 :param child: the nested symbol table. 685 :type child: :py:class:`fparser.two.symbol_table.SymbolTable` 687 :raises TypeError: if the supplied child is not a SymbolTable. 690 if not isinstance(child, SymbolTable):
692 f
"Expected a SymbolTable instance but got '{type(child).__name__}'" 698 Removes the named symbol table. 700 :param str name: the name of the child symbol table to delete (not \ 703 :raises KeyError: if the named table is not a child of this one. 708 if child.name == lname:
713 f
"Symbol table '{self.name}' does not contain a table named '{name}'" 719 :returns: the child (nested) symbol tables, if any. 720 :rtype: list of :py:class:`fparser.two.symbol_table.SymbolTable` 727 :returns: the top-level symbol table that contains the current \ 728 scoping region (symbol table). 729 :rtype: :py:class:`fparser.two.symbol_table.SymbolTable` 733 while current.parent:
734 current = current.parent
743 __all__ = [
"SymbolTableError",
"SymbolTables",
"SymbolTable",
"SYMBOL_TABLES"]
def get_declared_name(self, name)
def add_child(self, child)
def wildcard_import(self)
def enter_scope(self, name)
def del_child(self, name)
def add_data_symbol(self, name, primitive_type)
def add_use_symbols(self, name, only_list=None, rename_list=None)
def _store_symbols(self, rename_list)
def scoping_unit_classes(self)
def _validate_tuple_list(name, tlist)
def enable_checks(self, value)