Skip to content

Commit

Permalink
Combining additional #88 test with #88 fix and #91
Browse files Browse the repository at this point in the history
Disabling the Jinja cache results in the unique name generator state disappearing from the global Jinja context:

    >       return _UniqueNameGenerator.ensure_generator_in_globals(env.globals)('c', adj_base_token, '_', '_')
    E       TypeError: 'NoneType' object is not callable
    .tox/py35-test/lib/python3.5/site-packages/nunavut/lang/c.py:527: TypeError

This issue affects all languages. The following test modification indicates that the state gets replaced with None:

    ------------------------- src/nunavut/lang/__init__.py -------------------------
    index bd6524c..135a29a 100644
    @@ -226,7 +226,7 @@ class _UniqueNameGenerator:
        def ensure_generator_in_globals(cls, environment_globals: typing.Dict[str, typing.Any]) -> '_UniqueNameGenerator':
            from .. import TypeLocalGlobalKey

    -        if TypeLocalGlobalKey not in environment_globals:
    +        if environment_globals.get(TypeLocalGlobalKey) is None:
                environment_globals[TypeLocalGlobalKey] = cls()
            return typing.cast('_UniqueNameGenerator', environment_globals[TypeLocalGlobalKey])

Am stuck.

Progress on #91

1) Fixing a problem with unique identifier generation (Issue #88)
2) Generating C++ data structures (Issue #91)

Still TODO for Issue #91 is serialization for types.
  • Loading branch information
pavel-kirienko authored and thirtytwobits committed Nov 11, 2019
1 parent 5ab9cb7 commit 5c7a66f
Show file tree
Hide file tree
Showing 19 changed files with 666 additions and 187 deletions.
1 change: 1 addition & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
"getenv",
"ifdef",
"ifndef",
"inout",
"isfunction",
"isroutine",
"itertools",
Expand Down
118 changes: 118 additions & 0 deletions src/nunavut/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -429,3 +429,121 @@ def get_or_make_namespace(full_namespace: str) -> typing.Tuple[Namespace, bool]:

# The empty namespace
return get_or_make_namespace('')[0]

# +---------------------------------------------------------------------------+


class Dependencies:
"""
Data structure that contains a set of composite types and annotations (bool flags)
which constitute a set of dependencies for a set of DSDL objects.
"""

def __init__(self) -> None:
self.composite_types = set() # type: typing.Set[pydsdl.CompositeType]
self.uses_integer = False
self.uses_float = False
self.uses_variable_length_array = False
self.uses_array = False
self.uses_bool = False


class DependencyBuilder:
"""
Given a list of DSDL types this object builds a set of types that the given types use.
.. invisible-code-block: python
import pydsdl
from unittest.mock import MagicMock
from nunavut import DependencyBuilder
my_dependant_type_l2 = MagicMock(spec=pydsdl.CompositeType)
my_dependant_type_l2.parent_service = False
my_dependant_type_l2.attributes = []
my_dependant_type_l1 = MagicMock(spec=pydsdl.CompositeType)
my_dependant_type_l1.parent_service = False
my_dependant_type_l1.attributes = [MagicMock(data_type=my_dependant_type_l2)]
my_top_level_type = MagicMock(spec=pydsdl.CompositeType)
my_top_level_type.parent_service = False
my_top_level_type.attributes = [MagicMock(data_type=my_dependant_type_l1)]
direct_dependencies = DependencyBuilder(my_top_level_type).direct()
assert len(direct_dependencies.composite_types) == 1
assert my_dependant_type_l1 in direct_dependencies.composite_types
transitive_dependencies = DependencyBuilder(my_top_level_type).transitive()
assert len(transitive_dependencies.composite_types) == 2
assert my_dependant_type_l1 in transitive_dependencies.composite_types
assert my_dependant_type_l2 in transitive_dependencies.composite_types
:param dependant_types: A list of types to build dependencies for.
:type dependant_types: typing.Iterable[pydsdl.Any]
"""

def __init__(self, *dependant_types: pydsdl.Any):
self._dependent_types = dependant_types

def transitive(self) -> Dependencies:
"""
Build a set of all transitive dependencies for the dependent types
set for this builder.
"""
return self._build_dependency_list(self._dependent_types, True)

def direct(self) -> Dependencies:
"""
Build a set of all first-order dependencies for the dependent types
set for this builder.
"""
return self._build_dependency_list(self._dependent_types, False)

# +-----------------------------------------------------------------------+
# | PRIVATE
# +-----------------------------------------------------------------------+

@classmethod
def _build_dependency_list(cls, dependant_types: typing.Iterable[pydsdl.CompositeType], transitive: bool) \
-> Dependencies:
results = Dependencies()
for dependant in dependant_types:
cls._extract_dependent_types(cls._extract_data_types(dependant), transitive, results)
return results

@classmethod
def _extract_data_types(cls, t: pydsdl.CompositeType) -> typing.List[pydsdl.SerializableType]:
# Make a list of all attributes defined by this type
if isinstance(t, pydsdl.ServiceType):
return [attr.data_type for attr in t.request_type.attributes] + \
[attr.data_type for attr in t.response_type.attributes]
else:
return [attr.data_type for attr in t.attributes]

@classmethod
def _extract_dependent_types(cls,
dependant_types: typing.Iterable[pydsdl.Any],
transitive: bool,
inout_dependencies: Dependencies) -> None:
for dt in dependant_types:
if isinstance(dt, pydsdl.CompositeType):
if dt not in inout_dependencies.composite_types:
inout_dependencies.composite_types.add(dt)
if transitive:
cls._extract_dependent_types(cls._extract_data_types(dt), transitive, inout_dependencies)
elif isinstance(dt, pydsdl.ArrayType):
if isinstance(dt, pydsdl.VariableLengthArrayType):
inout_dependencies.uses_variable_length_array = True
else:
inout_dependencies.uses_array = True

cls._extract_dependent_types([dt.element_type], transitive, inout_dependencies)
elif isinstance(dt, pydsdl.IntegerType):
inout_dependencies.uses_integer = True
elif isinstance(dt, pydsdl.FloatType):
inout_dependencies.uses_float = True
elif isinstance(dt, pydsdl.BooleanType):
inout_dependencies.uses_bool = True
34 changes: 33 additions & 1 deletion src/nunavut/jinja/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,37 @@ def is_serializable(value: pydsdl.Any) -> bool:
""" # noqa: E501
return isinstance(value, pydsdl.SerializableType)

@staticmethod
def is_None(value: typing.Any) -> bool:
"""
Tests if a value is ``None``
.. invisible-code-block: python
from nunavut.jinja import Generator
assert Generator.is_None(None) is True
assert Generator.is_None(1) is False
"""
return (value is None)

@staticmethod
def is_padding(value: pydsdl.Field) -> bool:
"""
Tests if a value is a padding field.
.. invisible-code-block: python
from nunavut.jinja import Generator
from unittest.mock import MagicMock
import pydsdl
assert Generator.is_padding(MagicMock(spec=pydsdl.PaddingField)) is True
assert Generator.is_padding(MagicMock(spec=pydsdl.Field)) is False
"""
return isinstance(value, pydsdl.PaddingField)

# +-----------------------------------------------------------------------+

def __init__(self,
Expand Down Expand Up @@ -375,7 +406,8 @@ def __init__(self,
keep_trailing_newline=True,
lstrip_blocks=lstrip_blocks,
trim_blocks=trim_blocks,
auto_reload=False)
auto_reload=False,
cache_size=0)

self._add_language_support()

Expand Down
28 changes: 15 additions & 13 deletions src/nunavut/lang/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -218,31 +218,26 @@ def get_supported_languages(self) -> typing.Dict[str, Language]:
class _UniqueNameGenerator:
"""
Functor used by template filters to obtain a unique name within a given template.
This should be made available as a private global "_unique_name_generator" within
each template and should be reset after completing generation of a type.
This should be made available as a private global within each template.
"""

_index_map = {} # type: typing.Dict[str, typing.Dict[str, int]]

@classmethod
def ensure_generator_in_globals(cls, environment_globals: typing.Dict[str, typing.Any]) -> '_UniqueNameGenerator':
from .. import TypeLocalGlobalKey

if TypeLocalGlobalKey not in environment_globals:
if TypeLocalGlobalKey not in environment_globals or environment_globals[TypeLocalGlobalKey] is None:
environment_globals[TypeLocalGlobalKey] = cls()
return typing.cast('_UniqueNameGenerator', environment_globals[TypeLocalGlobalKey])

def __init__(self) -> None:
self._index_map = {} # type: typing.Dict[str, typing.Dict[str, int]]

def __call__(self, key: str, base_token: str, prefix: str, suffix: str) -> str:
"""
Uses a lazy internal index to generate a number unique to a given base_token within a template
for a given domain (key).
"""
@classmethod
def _get_next_globally_unique(cls, key: str, base_token: str, prefix: str, suffix: str) -> str:
try:
keymap = self._index_map[key]
keymap = cls._index_map[key]
except KeyError:
keymap = {}
self._index_map[key] = keymap
cls._index_map[key] = keymap

try:
next_index = keymap[base_token]
Expand All @@ -256,3 +251,10 @@ def __call__(self, key: str, base_token: str, prefix: str, suffix: str) -> str:
base_token=base_token,
index=next_index,
suffix=suffix)

def __call__(self, *args: typing.Any) -> str:
"""
Uses a global index to generate a number unique to a given base_token within a template
for a given domain (key).
"""
return self._get_next_globally_unique(*args)
12 changes: 9 additions & 3 deletions src/nunavut/lang/c.py
Original file line number Diff line number Diff line change
Expand Up @@ -326,15 +326,21 @@ def to_c_float(self) -> str:
else:
return 'double'

def to_c_type(self, value: pydsdl.PrimitiveType, use_standard_types: bool = True) -> str:
def to_c_type(self,
value: pydsdl.PrimitiveType,
use_standard_types: bool = True,
inttype_prefix: typing.Optional[str] = None) -> str:
safe_prefix = '' if inttype_prefix is None else inttype_prefix
if isinstance(value, pydsdl.UnsignedIntegerType):
return (self.to_c_int(False) if not use_standard_types else self.to_std_int(False))
return safe_prefix + (self.to_c_int(False) if not use_standard_types else self.to_std_int(False))
elif isinstance(value, pydsdl.SignedIntegerType):
return (self.to_c_int(True) if not use_standard_types else self.to_std_int(True))
return safe_prefix + (self.to_c_int(True) if not use_standard_types else self.to_std_int(True))
elif isinstance(value, pydsdl.FloatType):
return self.to_c_float()
elif isinstance(value, pydsdl.BooleanType):
return ('BOOL' if not use_standard_types else 'bool')
elif isinstance(value, pydsdl.VoidType):
return 'void'
else:
raise RuntimeError("{} is not a known PrimitiveType".format(type(value).__name__))

Expand Down
Loading

0 comments on commit 5c7a66f

Please sign in to comment.