import re
from typing import Optional
from hdlConvertorAst.py_ver_compatibility import is_str
[docs]class LanguageKeyword(object):
"""
Base class for keywords of target language.
Used to notify that the name represents a language keyword
and thus shall not be modified.
"""
pass
[docs]class NameOccupiedErr(Exception):
"""
An exception which is rised if the symbol name is used by a different
object in current scope
"""
[docs] def __init__(self, nameScope, name, usedOn):
super(NameOccupiedErr, self).__init__()
self.nameScope = nameScope
self.name = name
self.usedOn = usedOn
def __str__(self):
path = [self.name, ]
ns = self.nameScope
while ns is not None:
n = ns.name
if n is None:
n = "<DEFAULT>"
path.append(n)
ns = ns.parent
return "<%s in %s, usedOn=%r" % (self.__class__.__name__, "/".join(reversed(path)), self.usedOn)
[docs]class ObjectForNameNotFound(KeyError):
"""
And exception which is rised if the name is not registered at all.
"""
pass
[docs]class _INVALID():
[docs] def __init__(self):
raise AssertionError("This class meant to be used as a constant")
[docs]class NameScope(dict):
"""
Scope of used names in hdl (node of hierarchical symbol table).
Used to find an object for a HdlValueId or HdlValueId for an object and
to resolve collision between different object of a same name if required.
Contains mapping {name: obj}
if name is discovered in scope it is converted to name_id
where id is sequential number for prefix name\_
:ivar ~.level: describes how deeply nested is this NameScopeItem in
name hierarchy
:ivar ~.cntrsForPrefixNames: counters for prefix names (for each name which
has to be renamed there is a counter which is used to find a non occupied
name faster)
:ivar ~.reversed: a reverse dict to this dict ({obj: name})
:ivar ~.serializer_ctx: an object with metainformations private to a current
serializer
:ivar ~.children: a dictionary {object which caused a NameScope fork: new NameScope}
"""
RE_NON_ID_CHAR = re.compile('[^A-Za-z_$0-9]', re.MULTILINE)
RE_LETTER = re.compile("[A-Za-z]")
[docs] @classmethod
def _sanitize_name(self, suggested_name: str) -> str:
name = self.RE_NON_ID_CHAR.sub("_", suggested_name)
if not self.RE_LETTER.match(name[0]):
return "v" + name
else:
return name
[docs] @classmethod
def make_top(cls, ignorecase):
"""
Syntax shugar, call constructor with argument prefilled for a top NameScope
:type ignorecase: bool
:return: NameScope
"""
return cls(None, None, ignorecase)
[docs] def __init__(self, parent, name, ignorecase, debug=False):
"""
:type parent: Optional[NameScope]
:param name: name of object which this namescope belongs to
:type name: Optional[str]
:note: parent=None, name=None for global namescope
:param ignorecase: if True the name comparison does not
care about lowercase/uppercase
:param debug: If True name scopes are not required to have
forward declarations and for any object some name
is always resolved without raising definition errors
"""
super(NameScope, self).__init__()
self.parent = parent
self.ignorecase = ignorecase
# some names are specified just as prefix and serializer
# should resolve correct name for object
# this happens for most of generated objects
self.cntrsForPrefixNames = {}
self.reversed = {}
self.serializer_ctx = None
self.children = {}
self.name = name
self.debug = debug
[docs] def update(self, other):
for k, v in other.items():
self.register_name(k, v)
# @internal
def __incrPrefixCntr(self, prefix, currentVal):
"""
:return: str
"""
# [TODO] check if new name is not defined in any direction (parent-children)
currentVal += 1
self.cntrsForPrefixNames[prefix] = currentVal
usableName = prefix + str(currentVal)
return usableName
# @internal
[docs] def register_name(self, name, obj):
# search if name is already defined on me and parents
actual = self
o = None
if self.ignorecase:
_name = name.lower()
else:
_name = name
while actual is not None:
try:
o = actual[_name]
except KeyError:
actual = actual.parent
continue
break
if o is obj:
pass
elif o is None:
# we can use use the name, because it is not used
# in any parent and that means we are not redefinig the symbol
self[_name] = obj
self.reversed[obj] = _name
else:
raise NameOccupiedErr(self, name, o)
[docs] def get_child(self, name):
assert is_str(name), name
if self.ignorecase:
name = name.lower()
return self.children[name]
[docs] def level_push(self, name):
assert is_str(name), name
if self.ignorecase:
name = name.lower()
i = self.children.get(name, None)
if i is not None:
# there is already a child with such a name
return i
if name not in self:
if self.debug:
self[name] = object()
else:
raise AssertionError(
name, "name not registered for any object in this scope")
i = self.__class__(self, name, self.ignorecase)
self.children[name] = i
return i
[docs] def level_pop(self):
return self.parent
[docs] def checked_name(self, suggested_name, obj):
"""
Get a non occupied name in current scope
if name is occupied or name ends with _ the new
name is generated.
:return: str
"""
assert is_str(suggested_name), suggested_name
suggested_name = self._sanitize_name(suggested_name)
if not suggested_name.endswith("_"):
try:
self.register_name(suggested_name, obj)
return suggested_name
except NameOccupiedErr:
suggested_name += "_"
actual = self
while actual is not None:
try:
cntrVal = actual.cntrsForPrefixNames[suggested_name]
break
except KeyError:
actual = actual.parent
if actual is not None:
# some parrent of self already have such a prefix counter
usableName = actual.__incrPrefixCntr(
suggested_name, cntrVal)
else:
# parrents and self does not have such a prefix counter
# delete potentially existing prefix counterrs from children
# and add prefix counter to self
cntrVal = self.__discard_prefix_cntrs_from_children(suggested_name)
usableName = self.__incrPrefixCntr(
suggested_name, cntrVal)
# setup for me and propagate to children
self.register_name(usableName, obj)
return usableName
def __discard_prefix_cntrs_from_children(self, prefix):
"""
Discard all prefix counters from all childrens in order to prevent
children from looping trought all occupied names.
"""
cntr_val = self.cntrsForPrefixNames.pop(prefix, -1)
for c in self.children.values():
cntr_val = max(
cntr_val, c.__discard_prefix_cntrs_from_children(prefix))
return cntr_val
[docs] def get_object_and_scope_by_name(self, name):
assert is_str(name), name
if self.ignorecase:
name = name.lower()
actual = self
while actual is not None:
o = actual.get(name, _INVALID)
if o is not _INVALID:
return (actual, o)
else:
actual = actual.parent
raise KeyError(name)
[docs] def get_object_name(self, obj):
"""
:return: str
"""
assert obj is not None
actual = self
while actual is not None:
n = actual.reversed.get(obj, None)
if n is not None:
return n
actual = actual.parent
raise ObjectForNameNotFound(obj)
[docs]class WithNameScope():
"""
A syntax shugar, context manager which temporarly swaps the name_scope property on object
"""
[docs] def __init__(self, obj, name_scope):
"""
:type obj: an object which does have a name_scope stored in name_scope property
:type name_scope: NameScope
"""
self.obj = obj
self.name_scope = name_scope
def __enter__(self):
self.original_name_scope = self.obj.name_scope
self.obj.name_scope = self.name_scope
def __exit__(self, type, value, traceback):
self.obj.name_scope = self.original_name_scope