321 lines
8.8 KiB
Python
321 lines
8.8 KiB
Python
from __future__ import annotations
|
|
|
|
import functools
|
|
import operator
|
|
import weakref
|
|
from collections import defaultdict
|
|
from copy import copy
|
|
|
|
from .exceptions import ParseException
|
|
from .infos import LineIndexInfo
|
|
from .util import asjson, join_lists
|
|
|
|
DEFAULT_SEPARATOR = '.'
|
|
|
|
|
|
def join_symtables(tables):
|
|
def join_namespaces(base, target):
|
|
base = copy(base)
|
|
for symbol in target.symbols:
|
|
name = symbol.name
|
|
if name not in base or base.duplicates:
|
|
base.insert(symbol)
|
|
else:
|
|
join_namespaces(base[name], symbol)
|
|
return base
|
|
|
|
if not tables:
|
|
return {}
|
|
|
|
result = copy(tables[0])
|
|
for table in tables[1:]:
|
|
result = join_namespaces(table, result)
|
|
return result
|
|
|
|
|
|
class SymbolTableError(ParseException):
|
|
pass
|
|
|
|
|
|
class Namespace:
|
|
def __init__(
|
|
self, ignorecase=False, duplicates=False, separator=DEFAULT_SEPARATOR,
|
|
):
|
|
super().__init__()
|
|
self.ignorecase = ignorecase
|
|
self.duplicates = duplicates
|
|
self.separator = separator
|
|
self._entries = defaultdict(list)
|
|
|
|
@property
|
|
def entries(self):
|
|
return self._entries
|
|
|
|
@property
|
|
def symbols(self):
|
|
return join_lists(self._entries.values())
|
|
|
|
@property
|
|
def names(self):
|
|
return list(self._entries.keys())
|
|
|
|
def all_names(self):
|
|
result = []
|
|
for name, symbols in self.entries.items():
|
|
result.append(name)
|
|
for symbol in symbols:
|
|
result.extend(symbol.all_names())
|
|
return result
|
|
|
|
def __contains__(self, name):
|
|
if self.ignorecase:
|
|
name = name.upper()
|
|
return name in self.entries
|
|
|
|
def __getitem__(self, name):
|
|
if self.ignorecase:
|
|
name = name.upper()
|
|
if self.duplicates:
|
|
return self.entries.get(name)
|
|
elif name in self.entries:
|
|
return self.entries[name][0]
|
|
else:
|
|
raise KeyError(name)
|
|
|
|
def get(self, name, default=None):
|
|
return self.entries.get(name, default)
|
|
|
|
def insert(self, symbol):
|
|
assert isinstance(
|
|
symbol.name, str,
|
|
), f'"{symbol.name!s}" is not a valid symbol name'
|
|
name = symbol.name
|
|
if self.ignorecase:
|
|
name = name.upper()
|
|
if name in self._entries and not self.duplicates:
|
|
raise SymbolTableError(
|
|
f'Symbol "{symbol.name!s}" already in namespace',
|
|
)
|
|
|
|
self._entries[name].append(symbol)
|
|
|
|
def lookup_all(self, qualname, drill=True):
|
|
if self.ignorecase:
|
|
qualname = qualname.upper()
|
|
return self._lookup_drilldown(
|
|
qualname.split(self.separator), drill=drill,
|
|
)
|
|
|
|
def lookup(self, qualname, drill=True):
|
|
if self.ignorecase:
|
|
qualname = qualname.upper()
|
|
result = self._lookup_drilldown(
|
|
qualname.split(self.separator), drill=drill, max=1,
|
|
)
|
|
return result[0] if result else None
|
|
|
|
def _lookup_drilldown(self, namelist, drill=True, max=None):
|
|
if not namelist:
|
|
return []
|
|
|
|
result = []
|
|
for symbol in self.symbols:
|
|
found = symbol._lookup_drilldown(namelist, drill=drill, max=max)
|
|
result.extend(found)
|
|
if max and len(result) >= max:
|
|
break
|
|
return result
|
|
|
|
def resolve(self, qualname):
|
|
return self.lookup(qualname)
|
|
|
|
def filter(self, condition):
|
|
return functools.reduce(
|
|
operator.iadd,
|
|
(symbol.filter(condition) for symbol in self.symbols),
|
|
[],
|
|
)
|
|
|
|
def filter_first(self, condition):
|
|
for symbol in self.symbols:
|
|
result = symbol.filter_first(condition)
|
|
if result:
|
|
return result
|
|
return None
|
|
|
|
def all(self):
|
|
return self.filter(lambda: True)
|
|
|
|
def asjson(self):
|
|
return asjson(self)
|
|
|
|
def __repr__(self):
|
|
return f'{type(self).__name__}[]'
|
|
|
|
def __json__(self, seen=None):
|
|
return {
|
|
name: asjson(symbols, seen=seen)
|
|
for name, symbols in self.entries.items()
|
|
}
|
|
|
|
|
|
class SymbolTable(Namespace):
|
|
def add_reference(self, qualname, from_node):
|
|
symbol = self.lookup(qualname)
|
|
symbol.add_reference(qualname, from_node)
|
|
|
|
|
|
class Symbol(Namespace):
|
|
def __init__(self, name, node, ignorecase=False, duplicates=False):
|
|
super().__init__(ignorecase=ignorecase, duplicates=duplicates)
|
|
if not isinstance(name, str):
|
|
raise TypeError(f'"{name}" is not a valid symbol name')
|
|
self.name = name
|
|
self._node = node
|
|
self._parent = None
|
|
self._references = []
|
|
|
|
@property
|
|
def node(self):
|
|
return self._node
|
|
|
|
@property
|
|
def line(self):
|
|
return self.node.line
|
|
|
|
@property
|
|
def endline(self):
|
|
return self.node.endline
|
|
|
|
@property
|
|
def parent(self):
|
|
if self._parent is not None:
|
|
return self._parent()
|
|
return None
|
|
|
|
@property
|
|
def references(self):
|
|
return self._references
|
|
|
|
def insert(self, symbol):
|
|
super().insert(symbol)
|
|
symbol._parent = weakref.ref(self)
|
|
|
|
def qualpath(self):
|
|
if self.parent:
|
|
return [*self.parent.qualpath(), self.name]
|
|
return [self.name]
|
|
|
|
def qualname(self, sep=DEFAULT_SEPARATOR):
|
|
return sep.join(self.qualpath())
|
|
|
|
def _lookup_drilldown(self, namelist, drill=True, max=None):
|
|
if self.ignorecase and [self.name.upper()] == namelist:
|
|
return [self]
|
|
elif not self.ignorecase and [self.name] == namelist:
|
|
return [self]
|
|
elif self.name == namelist[0]:
|
|
return super()._lookup_drilldown(
|
|
namelist[1:], drill=drill, max=max,
|
|
)
|
|
elif drill:
|
|
return super()._lookup_drilldown(namelist, drill=drill, max=max)
|
|
else:
|
|
return []
|
|
|
|
def resolve(self, qualname):
|
|
return (
|
|
self.lookup(qualname) or
|
|
(self.parent and self.parent.resolve(qualname))
|
|
)
|
|
|
|
def filter(self, condition):
|
|
this_case = [self] if condition(self) else []
|
|
return this_case + super().filter(condition)
|
|
|
|
def filter_first(self, condition):
|
|
if condition(self):
|
|
return self
|
|
return super().filter(condition)
|
|
|
|
def add_reference(self, qualname, node):
|
|
# reference = SymbolReference(self, qualname, node)
|
|
reference = node
|
|
if reference not in self.references:
|
|
self._references.append(reference)
|
|
|
|
def line_index(self, include_entries=False, include_references=False):
|
|
result = set(self.node.line_index())
|
|
|
|
if include_references:
|
|
result.update(self.reference_line_index())
|
|
|
|
if include_entries:
|
|
for s in self.symbols:
|
|
index = s.line_index(
|
|
include_entries=include_entries,
|
|
include_references=include_references,
|
|
)
|
|
result.update(index)
|
|
assert isinstance(result, set)
|
|
assert all(isinstance(i, LineIndexInfo) for i in result)
|
|
return sorted(result)
|
|
|
|
def reference_line_index(self):
|
|
result = set()
|
|
for r in self.references:
|
|
result.update(r.line_index())
|
|
assert isinstance(result, set)
|
|
assert all(isinstance(i, LineIndexInfo) for i in result)
|
|
return result
|
|
|
|
def __repr__(self):
|
|
return f'{self.name}[]'
|
|
|
|
def __json__(self, seen=None):
|
|
return dict(
|
|
('node', type(self.node).__name__),
|
|
('entries', super().__json__(seen=seen)),
|
|
('references', asjson(self._references, seen=seen)),
|
|
)
|
|
|
|
def __getstate__(self):
|
|
state = self.__dict__.copy()
|
|
state.update(_parent=self.parent)
|
|
return state
|
|
|
|
def __setstate__(self, state):
|
|
self.__dict__.update(state)
|
|
if self._parent is not None:
|
|
self._parent = weakref.ref(self._parent)
|
|
|
|
|
|
class BasedSymbol(Namespace):
|
|
def __init__(self, name, node, duplicates=False):
|
|
super().__init__(duplicates=duplicates)
|
|
self._bases = []
|
|
|
|
@property
|
|
def bases(self):
|
|
return self._bases
|
|
|
|
def add_base(self, base):
|
|
assert isinstance(base, Symbol)
|
|
self._bases.append(base)
|
|
|
|
def _lookup_drilldown(self, namelist, drill=True, max=max):
|
|
result = super()._lookup_drilldown(namelist, drill=drill, max=max)
|
|
if result:
|
|
return result
|
|
|
|
for base in self.bases:
|
|
result = base._lookup_drilldown(namelist, drill=drill, max=max)
|
|
if result:
|
|
return result
|
|
return result
|
|
|
|
def __json__(self, seen=None):
|
|
result = super().__json__(seen=seen)
|
|
result['bases'] = asjson([b.qualname() for b in self.bases], seen=seen)
|
|
return result
|