diff --git a/.vscode/settings.json b/.vscode/settings.json index f64f323b..5f54705f 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -56,6 +56,7 @@ "getenv", "ifdef", "ifndef", + "inout", "isfunction", "isroutine", "itertools", diff --git a/src/nunavut/__init__.py b/src/nunavut/__init__.py index b64c80dd..3d8f1570 100644 --- a/src/nunavut/__init__.py +++ b/src/nunavut/__init__.py @@ -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 diff --git a/src/nunavut/jinja/__init__.py b/src/nunavut/jinja/__init__.py index f9cf8088..eb62456f 100644 --- a/src/nunavut/jinja/__init__.py +++ b/src/nunavut/jinja/__init__.py @@ -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, @@ -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() diff --git a/src/nunavut/lang/__init__.py b/src/nunavut/lang/__init__.py index bd6524c5..3ef6d95f 100644 --- a/src/nunavut/lang/__init__.py +++ b/src/nunavut/lang/__init__.py @@ -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 TypeLocalGlobalKey not in environment_globals or environment_globals[TypeLocalGlobalKey] is None: environment_globals[TypeLocalGlobalKey] = cls() return typing.cast('_UniqueNameGenerator', environment_globals[TypeLocalGlobalKey]) diff --git a/src/nunavut/lang/c.py b/src/nunavut/lang/c.py index 1a8e42c8..f1dc6e32 100644 --- a/src/nunavut/lang/c.py +++ b/src/nunavut/lang/c.py @@ -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__)) diff --git a/src/nunavut/lang/cpp/__init__.py b/src/nunavut/lang/cpp/__init__.py index 612fb1ce..68fb5b2a 100644 --- a/src/nunavut/lang/cpp/__init__.py +++ b/src/nunavut/lang/cpp/__init__.py @@ -9,7 +9,6 @@ """ import io -import pathlib import re import typing @@ -17,8 +16,9 @@ from ... import SupportsTemplateEnv, templateEnvironmentFilter from ..c import (C_RESERVED_IDENTIFIERS, C_RESERVED_PATTERNS, - VariableNameEncoder) + VariableNameEncoder, _CFit) from ...lang import LanguageContext +from ._support import IncludeGenerator # Taken from https://en.cppreference.com/w/cpp/keyword @@ -246,7 +246,10 @@ def filter_id(instance: typing.Any, stropping_prefix: str = '_', encoding_prefix return CPP_NO_DOUBLE_DASH_RULE.sub('_' + vne.encode_character('_'), out) -def filter_open_namespace(full_namespace: str, bracket_on_next_line: bool = True, linesep: str = '\n') -> str: +def filter_open_namespace(full_namespace: str, + bracket_on_next_line: bool = True, + linesep: str = '\n', + stropping: bool = True) -> str: """ Emits c++ opening namspace syntax parsed from a pydsdl "full_namespace", dot-seperated value. @@ -288,7 +291,10 @@ def filter_open_namespace(full_namespace: str, bracket_on_next_line: bool = True with io.StringIO() as content: for name in full_namespace.split('.'): content.write('namespace ') - content.write(name) + if stropping: + content.write(filter_id(name)) + else: + content.write(name) if bracket_on_next_line: content.write(linesep) else: @@ -298,7 +304,10 @@ def filter_open_namespace(full_namespace: str, bracket_on_next_line: bool = True return content.getvalue() -def filter_close_namespace(full_namespace: str, omit_comments: bool = False, linesep: str = '\n') -> str: +def filter_close_namespace(full_namespace: str, + omit_comments: bool = False, + linesep: str = '\n', + stropping: bool = True) -> str: """ Emits c++ closing namspace syntax parsed from a pydsdl "full_namespace", dot-seperated value. @@ -340,7 +349,10 @@ def filter_close_namespace(full_namespace: str, omit_comments: bool = False, lin content.write('}') if not omit_comments: content.write(' // namespace ') - content.write(name) + if stropping: + content.write(filter_id(name)) + else: + content.write(name) content.write(linesep) return content.getvalue() @@ -352,67 +364,101 @@ def filter_full_reference_name(t: pydsdl.CompositeType, stropping: bool = True) .. invisible-code-block: python from nunavut.lang.cpp import filter_full_reference_name + from unittest.mock import MagicMock + import pydsdl - dummy = lambda: None - dummy_version = lambda: None - setattr(dummy, 'version', dummy_version) + my_obj = MagicMock() + my_obj.parent_service = None + my_obj.version = MagicMock() .. code-block:: python - # Given - full_name = 'any.int.2Foo' - major = 1 - minor = 2 + # Given a type with illegal characters for C++ + my_obj.full_name = 'any.int.2Foo' + my_obj.version.major = 1 + my_obj.version.minor = 2 # and template = '{{ my_obj | full_reference_name }}' - # then + # then, with stropping enabled rendered = 'any::_int::_2Foo_1_2' .. invisible-code-block: python + my_obj.short_name = my_obj.full_name.split('.')[-1] + jinja_filter_tester(filter_full_reference_name, template, rendered, my_obj=my_obj) - setattr(dummy_version, 'major', major) - setattr(dummy_version, 'minor', minor) - setattr(dummy, 'full_name', full_name) - setattr(dummy, 'short_name', full_name.split('.')[-1]) - jinja_filter_tester(filter_full_reference_name, template, rendered, my_obj=dummy) - + my_obj = MagicMock() + my_obj.version = MagicMock() + my_obj.parent_service = None .. code-block:: python - # Given - full_name = 'any.int.2Foo' - major = 1 - minor = 2 + # Given a type with illegal characters for C++ + my_obj.full_name = 'any.int.2Foo' + my_obj.version.major = 1 + my_obj.version.minor = 2 # and template = '{{ my_obj | full_reference_name(stropping=False) }}' - # then + # then, with stropping disabled rendered = 'any::int::2Foo_1_2' .. invisible-code-block: python + my_obj.short_name = my_obj.full_name.split('.')[-1] + jinja_filter_tester(filter_full_reference_name, template, rendered, my_obj=my_obj) + + .. invisible-code-block: python + + my_obj = MagicMock(spec=pydsdl.CompositeType) + my_obj.version = MagicMock() + my_service = MagicMock(spec=pydsdl.ServiceType) + my_service.parent_service = None + my_service.version = MagicMock() + my_service.attributes = { 'Request': my_obj } + my_obj.parent_service = my_service + + Note that for service types - setattr(dummy_version, 'major', major) - setattr(dummy_version, 'minor', minor) - setattr(dummy, 'full_name', full_name) - setattr(dummy, 'short_name', full_name.split('.')[-1]) - jinja_filter_tester(filter_full_reference_name, template, rendered, my_obj=dummy) + .. code-block:: python + + # Given a service type + my_service.full_name = 'my.Service' + my_service.version.major = 1 + my_service.version.minor = 8 + + # and + template = '{{ my_service.attributes["Request"] | full_reference_name }}' + + # then + rendered = 'my::Service_1_8::Request' + + .. invisible-code-block: python + + my_service.short_name = my_service.full_name.split('.')[-1] + my_obj.short_name = 'Request' + my_obj.full_name = my_service.full_name + '.' + my_obj.short_name + + jinja_filter_tester(filter_full_reference_name, template, rendered, my_service=my_service) :param pydsdl.CompositeType t: The DSDL type to get the fully-resolved reference name for. :param bool stropping: If True then the :func:`filter_id` filter is applied to each component in the identifier. """ ns_parts = t.full_name.split('.') - if len(ns_parts) > 1: - if stropping: - ns = list(map(filter_id, ns_parts[:-1])) - else: - ns = ns_parts[:-1] + if stropping: + ns = list(map(filter_id, ns_parts[:-1])) + else: + ns = ns_parts[:-1] + + if t.parent_service is not None: + assert len(ns) > 0 # Well-formed DSDL will never have a request or response type that isn't nested. + ns = ns[:-1] + [filter_short_reference_name(t.parent_service, stropping=stropping)] - return '::'.join(ns + [filter_short_reference_name(t, stropping)]) + full_path = ns + [filter_short_reference_name(t, stropping)] + return '::'.join(full_path) def filter_short_reference_name(t: pydsdl.CompositeType, stropping: bool = True) -> str: @@ -420,58 +466,60 @@ def filter_short_reference_name(t: pydsdl.CompositeType, stropping: bool = True) Provides a string that is a shorted version of the full reference name. This type is unique only within its namespace. - .. invisible-code-block: python + .. invisible-code-block: python from nunavut.lang.cpp import filter_short_reference_name + from unittest.mock import MagicMock + import pydsdl - dummy = lambda: None - dummy_version = lambda: None - setattr(dummy, 'version', dummy_version) + my_type = MagicMock(spec=pydsdl.StructureType) + my_type.version = MagicMock() + my_type.parent_service = None .. code-block:: python - # Given - short_name = '2Foo' - major = 1 - minor = 2 + # Given a type with illegal C++ characters + my_type.short_name = '2Foo' + my_type.version.major = 1 + my_type.version.minor = 2 # and - template = '{{ my_obj | short_reference_name }}' + template = '{{ my_type | short_reference_name }}' - # then + # then, with stropping enabled rendered = '_2Foo_1_2' .. invisible-code-block: python - setattr(dummy_version, 'major', major) - setattr(dummy_version, 'minor', minor) - setattr(dummy, 'short_name', short_name) - jinja_filter_tester(filter_short_reference_name, template, rendered, my_obj=dummy) + jinja_filter_tester(filter_short_reference_name, template, rendered, my_type=my_type) + my_type = MagicMock(spec=pydsdl.StructureType) + my_type.version = MagicMock() + my_type.parent_service = None .. code-block:: python - # Given - short_name = '2Foo' - major = 1 - minor = 2 + # Given a type with illegal C++ characters + my_type.short_name = '2Foo' + my_type.version.major = 1 + my_type.version.minor = 2 # and - template = '{{ my_obj | short_reference_name(stropping=False) }}' + template = '{{ my_type | short_reference_name(stropping=False) }}' # then rendered = '2Foo_1_2' .. invisible-code-block: python - setattr(dummy_version, 'major', major) - setattr(dummy_version, 'minor', minor) - setattr(dummy, 'short_name', short_name) - jinja_filter_tester(filter_short_reference_name, template, rendered, my_obj=dummy) + jinja_filter_tester(filter_short_reference_name, template, rendered, my_type=my_type) :param pydsdl.CompositeType t: The DSDL type to get the reference name for. """ - short_name = '{short}_{major}_{minor}'.format(short=t.short_name, major=t.version.major, minor=t.version.minor) + if t.parent_service is None: + short_name = '{short}_{major}_{minor}'.format(short=t.short_name, major=t.version.major, minor=t.version.minor) + else: + short_name = t.short_name if stropping: return filter_id(short_name) else: @@ -482,6 +530,8 @@ def filter_short_reference_name(t: pydsdl.CompositeType, stropping: bool = True) def filter_includes(env: SupportsTemplateEnv, t: pydsdl.CompositeType, sort: bool = True, + prefer_system_includes: bool = False, + use_standard_types: bool = True, stropping: bool = True) -> typing.List[str]: """ Returns a list of all include paths for a given type. @@ -491,30 +541,168 @@ def filter_includes(env: SupportsTemplateEnv, :param bool strop: If true the list will contained stropped identifiers. :return: a list of include headers needed for a given type. """ - # Make a list of all attributes defined by this type - if isinstance(t, pydsdl.ServiceType): - atr = t.request_type.attributes + t.response_type.attributes + + include_gen = IncludeGenerator(t, filter_id, filter_short_reference_name, use_standard_types, stropping) + return include_gen.generate_include_filepart_list(LanguageContext.get_from_globals( + env.globals).get_output_extension(), + sort, + prefer_system_includes) + + +def filter_declaration(instance: pydsdl.Any, use_standard_types: bool = True) -> str: + """ + Emit a declaration statement for the given instance. + """ + if isinstance(instance, pydsdl.PrimitiveType) or isinstance(instance, pydsdl.VoidType): + return filter_type_from_primitive(instance, use_standard_types) + elif isinstance(instance, pydsdl.VariableLengthArrayType): + return 'std::vector<{}>'.format(filter_declaration(instance.element_type, use_standard_types)) + elif isinstance(instance, pydsdl.ArrayType): + return 'std::Array<{}>'.format(filter_declaration(instance.element_type, use_standard_types)) + else: + return filter_full_reference_name(instance) + + +def filter_definition_begin(instance: pydsdl.CompositeType) -> str: + """ + Emit the start of a definition statement for a composite type. + + .. invisible-code-block: python + + from nunavut.lang.cpp import filter_definition_begin + from unittest.mock import MagicMock + import pytest + import pydsdl + + my_type = MagicMock(spec=pydsdl.StructureType) + my_type.version = MagicMock() + my_type.parent_service = None + + with pytest.raises(ValueError): + jinja_filter_tester(filter_definition_begin, '{{ my_type | definition_begin }}', '', my_type=MagicMock()) + + .. code-block:: python + + # Given a pydsdl.CompositeType "my_type": + my_type.short_name = 'Foo' + my_type.version.major = 1 + my_type.version.minor = 0 + + # and + template = '{{ my_type | definition_begin }}' + + # then + rendered = 'struct Foo_1_0' + + .. invisible-code-block: python + + jinja_filter_tester(filter_definition_begin, template, rendered, my_type=my_type) + + my_union_type = MagicMock(spec=pydsdl.UnionType) + my_union_type.version = MagicMock() + my_union_type.parent_service = None + + .. code-block:: python + + # Also, given a pydsdl.UnionType "my_union_type": + my_union_type.short_name = 'Foo' + my_union_type.version.major = 1 + my_union_type.version.minor = 0 + + # and + union_template = '{{ my_union_type | definition_begin }}' + + # then + rendered = 'union Foo_1_0' + + .. invisible-code-block: python + + jinja_filter_tester(filter_definition_begin, union_template, rendered, my_union_type=my_union_type) + + my_service_type = MagicMock(spec=pydsdl.ServiceType) + my_service_type.version = MagicMock() + my_service_type.parent_service = None + + .. code-block:: python + + # Finally, given a pydsdl.Servicetype "my_service_type": + my_service_type.short_name = 'Foo' + my_service_type.version.major = 1 + my_service_type.version.minor = 0 + + # and + template = '{{ my_service_type | definition_begin }}' + + # then + rendered = 'namespace Foo_1_0' + + .. invisible-code-block: python + + jinja_filter_tester(filter_definition_begin, template, rendered, my_service_type=my_service_type) + + """ + short_name = filter_short_reference_name(instance) + if isinstance(instance, pydsdl.StructureType): + return 'struct {}'.format(short_name) + elif isinstance(instance, pydsdl.UnionType): + return 'union {}'.format(short_name) + elif isinstance(instance, pydsdl.ServiceType): + return 'namespace {}'.format(short_name) + else: + raise ValueError('{} types cannot be redefined.'.format(type(instance).__name__)) + + +def filter_definition_end(instance: pydsdl.CompositeType) -> str: + """ + Emit the end of a definition statement for a composite type. + + .. invisible-code-block: python + + from nunavut.lang.cpp import filter_definition_end + from unittest.mock import MagicMock + import pytest + import pydsdl + + + with pytest.raises(ValueError): + jinja_filter_tester(filter_definition_end, '{{ my_type | definition_end }}', '', my_type=MagicMock()) + + my_type = MagicMock(spec=pydsdl.StructureType) + my_type.version = MagicMock() + my_type.short_name = 'Foo' + my_type.version.major = 1 + my_type.version.minor = 0 + + jinja_filter_tester(filter_definition_end, '{{ my_type | definition_end }}', ';', my_type=my_type) + + my_type = MagicMock(spec=pydsdl.UnionType) + my_type.version = MagicMock() + my_type.short_name = 'Foo' + my_type.version.major = 1 + my_type.version.minor = 0 + + jinja_filter_tester(filter_definition_end, '{{ my_type | definition_end }}', ';', my_type=my_type) + + my_type = MagicMock(spec=pydsdl.ServiceType) + my_type.version = MagicMock() + my_type.parent_service = None + my_type.short_name = 'Foo' + my_type.version.major = 1 + my_type.version.minor = 0 + + jinja_filter_tester(filter_definition_end, + '{{ my_type | definition_end }}', + ' // namespace Foo_1_0', + my_type=my_type) + + """ + if isinstance(instance, pydsdl.StructureType) or isinstance(instance, pydsdl.UnionType): + return ';' + elif isinstance(instance, pydsdl.ServiceType): + return ' // namespace {}'.format(filter_short_reference_name(instance)) else: - atr = t.attributes - - dep_types = \ - [(x.data_type, filter_short_reference_name(x.data_type)) - for x in atr if isinstance(x.data_type, pydsdl.CompositeType)] - dep_types += \ - [(x.data_type.element_type, filter_short_reference_name(x.data_type.element_type)) - for x in atr if - isinstance(x.data_type, pydsdl.ArrayType) and isinstance(x.data_type.element_type, pydsdl.CompositeType)] - - def make_ns_list(dt: pydsdl.SerializableType) -> typing.List[str]: - if stropping: - return [filter_id(x) for x in dt.full_namespace.split('.')] - else: - return typing.cast(typing.List[str], dt.full_namespace.split('.')) - - suffix = LanguageContext.get_from_globals(env.globals).get_output_extension() - path_list = [str(pathlib.Path(*make_ns_list(dt)) / pathlib.Path(sr).with_suffix(suffix)) - for dt, sr in dep_types] - - if sort: - path_list = sorted(path_list) - return path_list + raise ValueError('{} types cannot be redefined.'.format(type(instance).__name__)) + + +def filter_type_from_primitive(value: pydsdl.PrimitiveType, use_standard_types: bool = True) -> str: + return _CFit.get_best_fit(value.bit_length).to_c_type(value, use_standard_types) diff --git a/src/nunavut/lang/cpp/_support.py b/src/nunavut/lang/cpp/_support.py new file mode 100644 index 00000000..26ac304a --- /dev/null +++ b/src/nunavut/lang/cpp/_support.py @@ -0,0 +1,72 @@ +# +# Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# Copyright (C) 2018-2019 UAVCAN Development Team +# This software is distributed under the terms of the MIT License. +# +""" + Additional, internal logic supporting C++ code generators. +""" + +import pathlib +import typing +import nunavut + +import pydsdl + + +class IncludeGenerator(nunavut.DependencyBuilder): + + def __init__(self, + t: pydsdl.CompositeType, + id_filter: typing.Callable[[str], str], + short_reference_name_filter: typing.Callable[..., str], + use_standard_types: bool, + stropping: bool): + super().__init__(t) + self._use_standard_types = use_standard_types + self._stropping = stropping + self._id = id_filter + self._short_reference_name_filter = short_reference_name_filter + + def generate_include_filepart_list(self, + output_extension: str, + sort: bool, + prefer_system_includes: bool) -> typing.List[str]: + dep_types = self.direct() + + path_list = [self._make_path(dt, output_extension) for dt in dep_types.composite_types] + + if prefer_system_includes: + path_list_with_punctuation = ['<{}>'.format(p) for p in path_list] + else: + path_list_with_punctuation = ['"{}"'.format(p) for p in path_list] + + if sort: + return self._get_std_includes(dep_types) + sorted(path_list_with_punctuation) + else: + return self._get_std_includes(dep_types) + path_list_with_punctuation + + # +-----------------------------------------------------------------------+ + # | PRIVATE + # +-----------------------------------------------------------------------+ + def _get_std_includes(self, dep_types: nunavut.Dependencies) -> typing.List[str]: + std_includes = [] # type: typing.List[str] + if self._use_standard_types: + if dep_types.uses_integer: + std_includes.append('cstdint') + if dep_types.uses_array: + std_includes.append('array') + if dep_types.uses_variable_length_array: + std_includes.append('vector') + return ['<{}>'.format(include) for include in sorted(std_includes)] + + def _make_path(self, dt: pydsdl.CompositeType, output_extension: str) -> str: + short_name = self._short_reference_name_filter(dt, stropping=self._stropping) + ns_path = pathlib.Path(*self._make_ns_list(dt)) / pathlib.Path(short_name).with_suffix(output_extension) + return str(ns_path) + + def _make_ns_list(self, dt: pydsdl.SerializableType) -> typing.List[str]: + if self._stropping: + return [self._id(x) for x in dt.full_namespace.split('.')] + else: + return typing.cast(typing.List[str], dt.full_namespace.split('.')) diff --git a/src/nunavut/lang/cpp/templates/Header.j2 b/src/nunavut/lang/cpp/templates/Header.j2 index b95a5ce0..d8e011d7 100644 --- a/src/nunavut/lang/cpp/templates/Header.j2 +++ b/src/nunavut/lang/cpp/templates/Header.j2 @@ -27,19 +27,14 @@ #ifndef {{T.full_name | c.macrofy}} #define {{T.full_name | c.macrofy}} -#include -{% for n in T | includes -%} -#include "{{ n }}" -{%- endfor %} - -{{T.full_namespace | cpp.open_namespace}} - -{%- block object %}{% endblock -%} - -{{T.full_namespace | cpp.close_namespace}} - +{% for n in T | includes(prefer_system_includes=False) -%} +#include {{ n }} +{% endfor %} +{{T.full_namespace | open_namespace}} +{%- block object -%}{%- endblock -%} +{{T.full_namespace | close_namespace}} #endif // {{T.full_name | c.macrofy}} /* {{ T | yamlfy }} -*/ \ No newline at end of file +*/ diff --git a/src/nunavut/lang/cpp/templates/ServiceType.j2 b/src/nunavut/lang/cpp/templates/ServiceType.j2 index 8dc516f0..6f4d9a4f 100644 --- a/src/nunavut/lang/cpp/templates/ServiceType.j2 +++ b/src/nunavut/lang/cpp/templates/ServiceType.j2 @@ -1,14 +1,10 @@ {% extends "Header.j2" %} -{% block object %} - -class {{ T.short_name }} +{%- block object -%} +{{ T | definition_begin }} { -{% for attribute in T.attributes %} - // {{ attribute }} -{% endfor %} -{% for field in T.fields %} - // {{ field }} -{% endfor %} -}; - -{% endblock %} \ No newline at end of file +{% set composite_type = T.request_type -%} +{% include '_composite_type.j2' %} +{% set composite_type = T.response_type -%} +{% include '_composite_type.j2' %} +}{{ T | definition_end }} +{% endblock -%} \ No newline at end of file diff --git a/src/nunavut/lang/cpp/templates/StructureType.j2 b/src/nunavut/lang/cpp/templates/StructureType.j2 index dda92d3e..d763941e 100644 --- a/src/nunavut/lang/cpp/templates/StructureType.j2 +++ b/src/nunavut/lang/cpp/templates/StructureType.j2 @@ -1,17 +1,5 @@ {% extends "Header.j2" %} -{% block object %} - -struct {{ T.short_name }} -{ -{%- for constant in T.constants %} - static constexpr {{ constant.data_type | c.type_from_primitive }} {{ constant.name }} = {{ constant.value.native_value.numerator }} / {{ constant.value.native_value.denominator }}; -{%- endfor %} -{% for field in T.fields -%} -{% if field.data_type is primitive %} - {{ field.data_type | c.type_from_primitive }} {{ field.name }}; -{%- endif %} -{%- endfor %} - -}; - -{% endblock %} \ No newline at end of file +{%- block object -%} +{% set composite_type = T -%} +{% include '_composite_type.j2' %} +{% endblock -%} \ No newline at end of file diff --git a/src/nunavut/lang/cpp/templates/UnionType.j2 b/src/nunavut/lang/cpp/templates/UnionType.j2 index 8dc516f0..d763941e 100644 --- a/src/nunavut/lang/cpp/templates/UnionType.j2 +++ b/src/nunavut/lang/cpp/templates/UnionType.j2 @@ -1,14 +1,5 @@ {% extends "Header.j2" %} -{% block object %} - -class {{ T.short_name }} -{ -{% for attribute in T.attributes %} - // {{ attribute }} -{% endfor %} -{% for field in T.fields %} - // {{ field }} -{% endfor %} -}; - -{% endblock %} \ No newline at end of file +{%- block object -%} +{% set composite_type = T -%} +{% include '_composite_type.j2' %} +{% endblock -%} \ No newline at end of file diff --git a/src/nunavut/lang/cpp/templates/_composite_type.j2 b/src/nunavut/lang/cpp/templates/_composite_type.j2 new file mode 100644 index 00000000..d132a57f --- /dev/null +++ b/src/nunavut/lang/cpp/templates/_composite_type.j2 @@ -0,0 +1,11 @@ +{{ composite_type | definition_begin }} +{ +{%- for constant in composite_type.constants %} + static constexpr {{ constant.data_type | declaration }} {{ constant.name | id }} = {{ constant.value.native_value.numerator }} / {{ constant.value.native_value.denominator }}; +{%- endfor -%} +{% for field in composite_type.fields -%} +{%- if field is not padding %} + {{ field.data_type | declaration }} {{ field.name | id }}; +{%- endif -%} +{%- endfor %} +}{{ composite_type | definition_end }} \ No newline at end of file diff --git a/src/nunavut/version.py b/src/nunavut/version.py index a95b98f6..933ff77d 100644 --- a/src/nunavut/version.py +++ b/src/nunavut/version.py @@ -3,6 +3,6 @@ # This software is distributed under the terms of the MIT License. # -__version__ = "0.1.12" +__version__ = "0.1.13" __license__ = 'MIT' diff --git a/test/gentest_filters/test_filters.py b/test/gentest_filters/test_filters.py index fbe8fc17..260f615d 100644 --- a/test/gentest_filters/test_filters.py +++ b/test/gentest_filters/test_filters.py @@ -210,10 +210,37 @@ def test_python_filter_includes(gen_paths, stropping, sort): # type: ignore mock_environment.globals = {LanguageContext.ContextInstanceGlobalKey: mock_language_context} imports = filter_includes(mock_environment, test_subject, sort=sort, stropping=stropping) - assert len(imports) == 3 + assert len(imports) == 5 + + def assert_path_in_imports(path: str) -> None: + nonlocal imports + assert path in imports + if stropping: - assert '_new/malloc_1_0.h' == imports[0] + if sort: + assert ['', + '', + '"_new/malloc_1_0.h"', + '"uavcan/str/bar_1_0.h"', + '"uavcan/time/SynchronizedTimestamp_1_0.h"' + ] == imports + else: + + map(assert_path_in_imports, ('', + '', + '"uavcan/time/SynchronizedTimestamp_1_0.h"', + '"_new/malloc_1_0.h"', + '"uavcan/str/bar_1_0.h"')) + elif sort: + assert ['', + '', + '"new/malloc_1_0.h"', + '"uavcan/str/bar_1_0.h"', + '"uavcan/time/SynchronizedTimestamp_1_0.h"' + ] == imports else: - assert 'new/malloc_1_0.h' == imports[0] - assert 'uavcan/str/bar_1_0.h' == imports[1] - assert 'uavcan/time/SynchronizedTimestamp_1_0.h' == imports[2] + map(assert_path_in_imports, ('', + '', + '"uavcan/time/SynchronizedTimestamp_1_0.h"', + '"new/malloc_1_0.h"', + '"uavcan/str/bar_1_0.h"')) diff --git a/verification/cpp/suite/test_compiles.cpp b/verification/cpp/suite/test_compiles.cpp index 85f3c2ab..43d7beb7 100644 --- a/verification/cpp/suite/test_compiles.cpp +++ b/verification/cpp/suite/test_compiles.cpp @@ -10,7 +10,7 @@ * Temporary test as a placeholder while we wire up the build. */ TEST(SanityTest, DoesSomethingCompile) { - uavcan::time::TimeSystem a; + uavcan::time::TimeSystem_0_1 a; a.value = 1; ASSERT_EQ(1, a.value); }