Source code for nunavut.lang.c

#
# Copyright (C) OpenCyphal Development Team  <opencyphal.org>
# Copyright Amazon.com Inc. or its affiliates.
# SPDX-License-Identifier: MIT
#
"""
    Filters for generating C. All filters in this
    module will be available in the template's global namespace as ``c``.
"""

import enum
import fractions
import re
import typing

import pydsdl

from nunavut._dependencies import Dependencies
from nunavut._templates import (
    template_environment_list_filter,
    template_language_filter,
    template_language_list_filter,
    template_language_test,
    template_volatile_filter,
)
from nunavut._utilities import YesNoDefault, cached_property
from nunavut.jinja.environment import Environment
from nunavut.lang._common import IncludeGenerator, TokenEncoder, UniqueNameGenerator
from nunavut.lang._language import Language as BaseLanguage


class Language(BaseLanguage):
    """
    Concrete, C-specific :class:`nunavut.lang.Language` object.
    """

    @staticmethod
    def _handle_stropping_failure(
        encoder: TokenEncoder, stropped: str, token_type: str, pending_error: RuntimeError
    ) -> str:
        """
        If the generic stropping results in either `^_[A-Z]` or `^__` we handle the failure
        with c-specific logic.
        """
        m = re.match(r"^_+([A-Z]?)", stropped)
        if m:
            # Resolve the conflict between C's global identifier rules and our desire to use
            # '_' as a stropping prefix:
            return "_{}{}".format(m.group(1).lower(), stropped[m.end() :])

        # we couldn't help after all. raise the pending error.
        raise pending_error

    @cached_property
    def _token_encoder(self) -> TokenEncoder:
        """
        Caching getter to ensure we don't have to recompile TokenEncoders for each filter invocation.
        """
        return TokenEncoder(self, stropping_failure_handler=self._handle_stropping_failure)

    def get_includes(self, dep_types: Dependencies) -> typing.List[str]:
        std_includes = []  # type: typing.List[str]
        if self.get_config_value_as_bool("use_standard_types"):
            std_includes.append("stdlib.h")
            # we always include stdlib if standard types are in use since initializers
            # require the use of NULL
            if dep_types.uses_integer:
                std_includes.append("stdint.h")
            if dep_types.uses_bool:
                std_includes.append("stdbool.h")
            if dep_types.uses_primitive_static_array:
                # We include this for memset.
                std_includes.append("string.h")
        return [f"<{include}>" for include in sorted(std_includes)]

    def filter_id(self, instance: typing.Any, id_type: str = "any") -> str:
        raw_name = self.default_filter_id_for_target(instance)

        vne = self._token_encoder
        return vne.strop(raw_name, id_type)


[docs] @template_language_filter(__name__) def filter_id(language: Language, instance: typing.Any, id_type: str = "any") -> str: """ Filter that produces a valid C identifier for a given object. The encoding may not be reversible. .. invisible-code-block: python from nunavut.lang.c import filter_id .. code-block:: python # Given I = 'I \u2764 c' # and template = '{{ I | id }}' # then rendered = 'I_zX2764_c' .. invisible-code-block: python jinja_filter_tester(filter_id, template, rendered, 'c', I=I) .. code-block:: python # Given I = 'if' # and template = '{{ I | id }}' # then rendered = '_if' .. invisible-code-block: python jinja_filter_tester(filter_id, template, rendered, 'c', I=I) .. code-block:: python # Given I = '_Reserved' # and template = '{{ I | id }}' # then rendered = '_reserved' .. invisible-code-block: python jinja_filter_tester(filter_id, template, rendered, 'c', I=I) .. code-block:: python # Given I = 'EMACRO_TOKEN' # and template = '{{ I | id("macro") }}' # then rendered = '_eMACRO_TOKEN' .. invisible-code-block: python jinja_filter_tester(filter_id, template, rendered, 'c', I=I) :param any instance: Any object or data that either has a name property or can be converted to a string. :param str id_type: A type of identifier. For C this value can be 'typedef', 'macro', 'function', or 'enum'. use 'any' to apply stropping rules for all identifier types to the instance. :return: A token that is a valid identifier for C, is not a reserved keyword, and is transformed in a deterministic manner based on the provided instance. """ return language.filter_id(instance, id_type)
[docs] @template_language_filter(__name__) def filter_macrofy(language: Language, value: str) -> str: """ Filter to transform an input into a valid C preprocessor identifier token. .. invisible-code-block: python from nunavut.lang import Language, LanguageContextBuilder from nunavut.lang.c import filter_macrofy, filter_id from unittest.mock import MagicMock .. code-block:: python # Given template = '#ifndef {{ "my full name" | macrofy }}' # then rendered = '#ifndef MY_FULL_NAME' .. invisible-code-block: python jinja_filter_tester(filter_macrofy, template, rendered, 'c') Note that individual tokens are not stropped so the appearance of an identifier in the ``SCREAMING_SNAKE_CASE`` output my be different then the token as it appears on its own. For example: .. code-block:: python # "register" is reserved so it will be stropped if it appears as an # identifier... template = '''#ifndef {{ "namespaced.Type.register" | macrofy }} {{ "register" | id }} ''' # ...but it will not be stropped within the macro. rendered = '''#ifndef NAMESPACED_TYPE_REGISTER _register ''' .. invisible-code-block: python jinja_filter_tester([filter_macrofy, filter_id], template, rendered, 'c') If stropping is enabled, however, the entire token generated by this filter will be stropped: .. code-block:: python # Given template = '#ifndef {{ "_starts_with_underscore" | macrofy }}' # then rendered = '#ifndef _sTARTS_WITH_UNDERSCORE' .. invisible-code-block: python jinja_filter_tester(filter_macrofy, template, rendered, 'c') And again with stropping disabled: .. code-block:: python # Given template = '#ifndef {{ "_starts_with_underscore" | macrofy }}' # then with stropping disabled rendered = '#ifndef _STARTS_WITH_UNDERSCORE' .. invisible-code-block: python lctx = ( LanguageContextBuilder() .set_target_language("c") .set_target_language_configuration_override(Language.WKCV_ENABLE_STROPPING, False) .create() ) jinja_filter_tester(filter_macrofy, template, rendered, lctx) :param str value: The value to transform. :return: A valid C preprocessor identifier token. """ macrofied_value = filter_to_screaming_snake_case(str(value)) if not language.enable_stropping: return macrofied_value else: return language.filter_id(macrofied_value, "macro")
_CFit_T = typing.TypeVar("_CFit_T", bound="_CFit") @enum.unique class _CFit(enum.Enum): IN_8 = 8 IN_16 = 16 IN_32 = 32 IN_64 = 64 def to_std_int(self, is_signed: bool) -> str: return "{}int{}_t".format(("" if is_signed else "u"), self.value) def to_c_int(self, is_signed: bool) -> str: if self.value == 8: intname = "char" elif self.value == 16: intname = "int" elif self.value == 32: intname = "long" else: intname = "long long" if not is_signed: intname = "unsigned " + intname return intname def to_c_float(self) -> str: if self.value in (8, 16, 32): return "float" else: return "double" def to_c_type( self, value: pydsdl.PrimitiveType, language: BaseLanguage, inttype_prefix: typing.Optional[str] = None ) -> str: use_standard_types = language.get_config_value_as_bool("use_standard_types") safe_prefix = "" if not use_standard_types or inttype_prefix is None else inttype_prefix if isinstance(value, pydsdl.UnsignedIntegerType): 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 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 language.named_types["boolean"] elif isinstance(value, pydsdl.VoidType): return "void" else: raise RuntimeError("{} is not a known PrimitiveType".format(type(value).__name__)) @classmethod def get_best_fit(cls: typing.Type[_CFit_T], bit_length: int) -> _CFit_T: if bit_length <= 8: bestfit = _CFit.IN_8 elif bit_length <= 16: bestfit = _CFit.IN_16 elif bit_length <= 32: bestfit = _CFit.IN_32 elif bit_length <= 64: bestfit = _CFit.IN_64 else: raise RuntimeError( "Cannot emit a standard type for a primitive that is larger than 64 bits ({}).".format(bit_length) ) return cls(bestfit)
[docs] @template_language_filter(__name__) def filter_type_from_primitive(language: Language, value: pydsdl.PrimitiveType) -> str: """ Filter to transform a pydsdl :class:`~pydsdl.PrimitiveType` into a valid C type. .. invisible-code-block: python from nunavut.lang.c import filter_type_from_primitive import pydsdl .. code-block:: python # Given template = '{{ unsigned_int_32_type | type_from_primitive }}' # then rendered = 'uint32_t' .. invisible-code-block: python test_type = pydsdl.UnsignedIntegerType(32, pydsdl.PrimitiveType.CastMode.TRUNCATED) jinja_filter_tester(filter_type_from_primitive, template, rendered, 'c', unsigned_int_32_type=test_type) .. code-block:: python # Given template = '{{ int_64_type | type_from_primitive }}' # then rendered = 'int64_t' .. invisible-code-block: python test_type = pydsdl.SignedIntegerType(64, pydsdl.PrimitiveType.CastMode.SATURATED) jinja_filter_tester(filter_type_from_primitive, template, rendered, 'c', int_64_type=test_type) :param str value: The dsdl primitive to transform. :return: A valid C99 type name. :raises RuntimeError: If the primitive cannot be represented as a standard C type. .. invisible-code-block: python template = '{{ unsigned_int_32_type | type_from_primitive }}' class UnknownType(pydsdl.IntegerType): def __init__(self, bit_length): super().__init__(32, pydsdl.PrimitiveType.CastMode.TRUNCATED) self._bit_length = bit_length @property def inclusive_value_range(self): raise NotImplementedError def __str__(self): return 'test dummy' try: jinja_filter_tester(filter_type_from_primitive, template, 'foo', 'c', unsigned_int_32_type=UnknownType(32)) assert False except RuntimeError: pass try: jinja_filter_tester(filter_type_from_primitive, template, 'foo', 'c', unsigned_int_32_type=UnknownType(128)) assert False except RuntimeError: pass """ return _CFit.get_best_fit(value.bit_length).to_c_type(value, language)
_snake_case_pattern_0 = re.compile(r"[\W]+") # 'port.SubjectIDList' -> 'port_SubjectIDList' _snake_case_pattern_1 = re.compile(r"(?<=[A-Z])([A-Z][a-z]+)") # 'port_SubjectIDList' -> 'port_SubjectID_list' _snake_case_pattern_2 = re.compile(r"(?<=_)([A-Z])+") # 'port_SubjectID_list' -> 'port_subjectID_list' _snake_case_pattern_3 = re.compile(r"(?<=[a-z])([A-Z])+") # 'port_subjectID_list' -> 'port_subject_id_list'
[docs] def filter_to_snake_case(value: str) -> str: """ Filter to transform a string into a snake-case token. .. invisible-code-block: python from nunavut.lang.c import filter_to_snake_case .. code-block:: python # Given template = '{{ "scotec.mcu.Timer" | to_snake_case }} a();' # then rendered = 'scotec_mcu_timer a();' .. invisible-code-block: python jinja_filter_tester(filter_to_snake_case, template, rendered, 'c') .. code-block:: python # Given template = '{{ "scotec.mcu.TimerHelper" | to_snake_case }} b();' # then rendered = 'scotec_mcu_timer_helper b();' .. invisible-code-block: python jinja_filter_tester(filter_to_snake_case, template, rendered, 'c') .. code-block:: python # and Given template = '{{ "SCOTEC_MCU_TimerHelper" | to_snake_case }} b();' # then rendered = 'scotec_mcu_timer_helper b();' .. invisible-code-block: python jinja_filter_tester(filter_to_snake_case, template, rendered, 'c') template = '{{ " aa bb. cCcAAa_aAa_AAaAa_AAaA_a " | to_snake_case }}' rendered = 'aa_bb_c_cc_a_aa_a_aa_a_aa_aa_a_aa_a_a' jinja_filter_tester(filter_to_snake_case, template, rendered, 'c') :param str value: The string to transform into C snake-case. :return: A valid C99 token using the snake-case convention. """ pass0 = _snake_case_pattern_0.sub("_", str.strip(value)) pass1 = _snake_case_pattern_1.sub(lambda x: "_" + x.group(0).lower(), pass0) pass2 = _snake_case_pattern_2.sub(lambda x: x.group(0).lower(), pass1) return _snake_case_pattern_3.sub(lambda x: "_" + x.group(0).lower(), pass2).lower()
[docs] def filter_to_screaming_snake_case(value: str) -> str: """ Filter to transform a string into a SCREAMING_SNAKE_CASE token. .. invisible-code-block: python from nunavut.lang.c import filter_to_screaming_snake_case .. code-block:: python # Given template = '{{ "scotec.mcu.Timer" | to_screaming_snake_case }} a();' # then rendered = 'SCOTEC_MCU_TIMER a();' .. invisible-code-block: python jinja_filter_tester(filter_to_screaming_snake_case, template, rendered, 'c') """ return filter_to_snake_case(value).upper()
[docs] @template_volatile_filter def filter_to_template_unique_name(_: typing.Any, base_token: str) -> str: """ Filter that takes a base token and forms a name that is very likely to be unique within the template the filter is invoked. This name is also very likely to be a valid C identifier. .. IMPORTANT:: The exact tokens generated may change between major or minor versions of this library. The only guarantee provided is that the tokens will be stable for the same version of this library given the same input. Also note that name uniqueness is only likely within a given template. Between templates there is no guarantee of uniqueness and, since this library does not lex generated source, there is no guarantee that the generated name does not conflict with a name generated by another means. .. invisible-code-block: python from nunavut.lang.c import filter_to_template_unique_name from nunavut.lang._common import UniqueNameGenerator .. code-block:: python # Given template = '{{ "foo" | to_template_unique_name }},{{ "Foo" | to_template_unique_name }},' template += '{{ "fOO" | to_template_unique_name }}' # then rendered = '_foo0_,_foo1_,_fOO0_' .. invisible-code-block: python UniqueNameGenerator.reset() jinja_filter_tester(filter_to_template_unique_name, template, rendered, 'c') .. code-block:: python # Given template = '{{ "i like coffee" | to_template_unique_name }}' # then rendered = '_i like coffee0_' .. invisible-code-block: python UniqueNameGenerator.reset() jinja_filter_tester(filter_to_template_unique_name, template, rendered, 'c') :param str base_token: A token to include in the base name. :return: A name that is likely to be valid C identifier and is likely to be unique within the file generated by the current template. """ if len(base_token) > 0: adj_base_token = base_token[0:1].lower() + base_token[1:] else: adj_base_token = base_token return UniqueNameGenerator.get_instance()("c", adj_base_token, "_", "_")
[docs] @template_language_filter(__name__) def filter_short_reference_name(language: Language, t: pydsdl.CompositeType) -> str: """ Provides a string that is a shorted version of the full reference name. .. invisible-code-block: python from nunavut.lang.c import filter_short_reference_name from unittest.mock import MagicMock import pydsdl my_type = MagicMock(spec=pydsdl.StructureType) my_type.version = MagicMock() my_type.parent_service = None .. code-block:: python # Given a type with illegal C characters my_type.short_name = '_Foo' my_type.version.major = 1 my_type.version.minor = 2 # and template = '{{ my_type | short_reference_name }}' # then, with stropping enabled rendered = '_foo_1_2' .. invisible-code-block: python jinja_filter_tester(filter_short_reference_name, template, rendered, 'c', my_type=my_type) With stropping disabled: .. code-block:: python rendered = '_Foo_1_2' .. invisible-code-block: python lctx = ( LanguageContextBuilder() .set_target_language("c") .set_target_language_configuration_override(Language.WKCV_ENABLE_STROPPING, False) .create() ) jinja_filter_tester(filter_short_reference_name, template, rendered, lctx, my_type=my_type) :param pydsdl.CompositeType t: The DSDL type to get the reference name for. """ return language.filter_short_reference_name(t)
[docs] @template_language_list_filter(__name__) @template_environment_list_filter def filter_includes( language: Language, env: Environment, t: pydsdl.CompositeType, sort: bool = True ) -> typing.List[str]: """ Returns a list of all include paths for a given type. .. invisible-code-block: python from nunavut.lang.c import filter_includes from unittest.mock import MagicMock import pydsdl my_type = MagicMock(spec=pydsdl.UnionType) my_type.version = MagicMock() my_type.parent_service = None .. code-block:: python # Listing the includes for a union with only integer types: template = '''{% for include in my_type | includes -%} {{include}} {% endfor %}''' # stdint.h will normally be generated rendered = '''<stdint.h> <stdlib.h> ''' .. invisible-code-block: python jinja_filter_tester(filter_includes, template, rendered, 'c', my_type=my_type) .. code-block:: python # You can suppress std includes by setting use_standard_types to False under # nunavut.lang.c rendered = '' .. invisible-code-block: python lctx = ( LanguageContextBuilder() .set_target_language("c") .set_target_language_configuration_override("use_standard_types", False) .create() ) jinja_filter_tester(filter_includes, template, rendered, lctx, my_type=my_type) :param pydsdl.CompositeType t: The type to scan for dependencies. :param bool sort: If true the returned list will be sorted. :return: a list of include headers needed for a given type. """ try: omit_serialization_support = env.globals["nunavut"].support["omit"] except KeyError: omit_serialization_support = False return IncludeGenerator(language, t, omit_serialization_support).generate_include_filepart_list( language.extension, sort )
[docs] def filter_to_static_assertion_value(obj: typing.Any) -> int: """ .. invisible-code-block: python from nunavut.lang.c import filter_to_static_assertion_value Tries to convert a Python object into a value compatible with static comparisons in C. This allows stable comparison of static values in headers to promote consistency and version compatibility in generated code. Will raise a ValueError if the object provided does not (yet) have an available conversion in this function. .. invisible-code-block: python try: jinja_filter_tester(filter_to_static_assertion_value, '{{ 3.14 | to_static_assertion_value }}', 'foo', 'c') assert False except ValueError: pass Currently supported types are string: .. code-block:: python # given template = '{{ "Any" | to_static_assertion_value }}' # then rendered = '1556001108' .. invisible-code-block: python jinja_filter_tester(filter_to_static_assertion_value, template, rendered, 'c') int: .. code-block:: python # given template = '{{ 123 | to_static_assertion_value }}' # then rendered = '123' .. invisible-code-block: python jinja_filter_tester(filter_to_static_assertion_value, template, rendered, 'c') and bool: .. code-block:: python # given template = '{{ True | to_static_assertion_value }}' # then rendered = '1' .. invisible-code-block: python jinja_filter_tester(filter_to_static_assertion_value, template, rendered, 'c') """ if isinstance(obj, bool): return 1 if obj else 0 if isinstance(obj, int): return obj if isinstance(obj, str): from zlib import crc32 return crc32(bytearray(obj, "utf-8")) raise ValueError("Cannot convert object of type {} into an integer in a stable manner.".format(type(obj)))
[docs] @template_language_filter(__name__) def filter_constant_value(language: Language, constant: pydsdl.Constant) -> str: """ Renders the specified constant as a literal. This is a shorthand for :func:`filter_literal`. .. invisible-code-block: python from nunavut.lang.c import filter_constant_value from unittest.mock import MagicMock, PropertyMock import fractions import pydsdl my_true_constant = MagicMock() my_true_constant.data_type = MagicMock(spec=pydsdl.BooleanType) .. code-block:: python # given template = '{{ my_true_constant | constant_value }}' # then rendered = 'true' .. invisible-code-block: python jinja_filter_tester(filter_constant_value, template, rendered, 'c', my_true_constant=my_true_constant) Language configuration can control the output of some constant tokens. For example, to use non-standard true and false values in c: .. code-block:: python # given template = '{{ my_true_constant | constant_value }}' # then, if true = 'NUNAVUT_TRUE' in the named_values for nunavut.lang.c rendered = 'NUNAVUT_TRUE' .. invisible-code-block: python named_values = {'true': 'NUNAVUT_TRUE'} lctx = ( LanguageContextBuilder() .set_target_language("c") .set_target_language_configuration_override(Language.WKCV_NAMED_VALUES, named_values) .create() ) jinja_filter_tester(filter_constant_value, template, rendered, lctx, my_true_constant=my_true_constant) Floating point values are converted as fractions to ensure no python-specific transformations are applied: .. code-block:: python # given a float value using a fraction of 355/113 template = '{{ almost_pi | constant_value }}' # ...the rendered value with include that fraction as a division statement. rendered = '((float) (355.0 / 113.0))' .. invisible-code-block: python almost_pi = pydsdl.Rational(fractions.Fraction('355/113')) almost_pi_constant = pydsdl.Constant(pydsdl.FloatType(32, pydsdl.PrimitiveType.CastMode.TRUNCATED), 'almost_pi', almost_pi) jinja_filter_tester(filter_constant_value, template, rendered, 'c', almost_pi=almost_pi_constant) template = '{{ three | constant_value }}' # ...the rendered value with include that fraction as a division statement. rendered = '((float) 3.0)' three = pydsdl.Rational(fractions.Fraction('3.0')) three_constant = pydsdl.Constant(pydsdl.FloatType(32, pydsdl.PrimitiveType.CastMode.TRUNCATED), 'three', three) jinja_filter_tester(filter_constant_value, template, rendered, 'c', three=three_constant) """ return filter_literal(language, constant.value.native_value, constant.data_type)
[docs] @template_language_filter(__name__) def filter_literal( language: Language, value: typing.Union[fractions.Fraction, bool, int], ty: pydsdl.Any, cast_format: typing.Optional[str] = None, ) -> str: """ Renders the specified value of the specified type as a literal. """ if cast_format is None: maybe_cast_format = language.get_option("cast_format") if not isinstance(maybe_cast_format, str): raise RuntimeError("cast_format language option was missing or invalid.") cast_format = maybe_cast_format del maybe_cast_format if isinstance(ty, pydsdl.BooleanType): return str(language.valuetoken_true if value else language.valuetoken_false) elif isinstance(ty, pydsdl.IntegerType): out = ( str(value) + "U" * isinstance(ty, pydsdl.UnsignedIntegerType) + "L" * (ty.bit_length > 16) + "L" * (ty.bit_length > 32) ) assert isinstance(out, str) return out elif isinstance(ty, pydsdl.FloatType): if value.denominator == 1: expr = "{}.0".format(value.numerator) else: expr = "({}.0 / {}.0)".format(value.numerator, value.denominator) cast = filter_type_from_primitive(language, ty) return cast_format.format(type=cast, value=expr) else: raise ValueError("Cannot construct a literal from an instance of {}".format(type(ty).__name__))
[docs] @template_language_filter(__name__) def filter_full_reference_name(language: Language, t: pydsdl.CompositeType) -> str: """ Provides a string that is the full namespace, typename, major, and minor version for a given composite type. .. invisible-code-block: python from nunavut.lang.c import filter_full_reference_name from unittest.mock import MagicMock import pydsdl my_obj = MagicMock() my_obj.has_parent_service = False my_obj.version = MagicMock() .. code-block:: python # Given a type with illegal characters for C++ my_obj.full_name = 'any.int.2Foo' my_obj.full_namespace = 'any.int' my_obj.version.major = 1 my_obj.version.minor = 2 # and template = '{{ my_obj | full_reference_name }}' # 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, 'c', my_obj=my_obj) :param pydsdl.CompositeType t: The DSDL type to get the fully-resolved reference name for. """ ns = t.full_namespace.split(".") full_path = ns + [language.filter_short_reference_name(t, YesNoDefault.NO)] not_stropped = "_".join(full_path) if language.enable_stropping: return language.filter_id(not_stropped) else: return not_stropped
[docs] def filter_to_standard_bit_length(t: pydsdl.PrimitiveType) -> int: """ Returns the nearest standard bit length of a type as an int. .. invisible-code-block: python from nunavut.lang.c import filter_to_standard_bit_length import pydsdl .. code-block:: python # Given I = pydsdl.UnsignedIntegerType(7, pydsdl.PrimitiveType.CastMode.TRUNCATED) # and template = '{{ I | to_standard_bit_length }}' # then rendered = '8' .. invisible-code-block: python jinja_filter_tester(filter_to_standard_bit_length, template, rendered, 'c', I=I) """ return int(_CFit.get_best_fit(t.bit_length).value)
[docs] @template_language_test(__name__) def is_zero_cost_primitive(language: Language, t: pydsdl.PrimitiveType) -> bool: """ Assuming that the target platform is IEEE754-conformant detects whether the native in-memory representation of a value of the supplied primitive type is the same as its on-the-wire representation defined by the DSDL Specification. For instance; all little-endian, IEEE754-conformant platforms have compatible in-memory representations of int8, int16, int32, int64, uint8, uint16, uint32, uint64, float32, float64. Values of other primitive types typically require some transformations (e.g., float16). It follows that arrays, certain composite types, and some other entities composed of zero-cost composites are also zero-cost types, but such non-trivial conjectures are not recognized by this function. Raises a :class:`TypeError` if the argument is not a value of type :class:`pydsdl.PrimitiveType`. .. invisible-code-block: python from nunavut.lang.c import is_zero_cost_primitive import pydsdl .. code-block:: python # Given i7 = pydsdl.SignedIntegerType(7, pydsdl.PrimitiveType.CastMode.SATURATED) u32 = pydsdl.UnsignedIntegerType(32, pydsdl.PrimitiveType.CastMode.TRUNCATED) f16 = pydsdl.FloatType(16, pydsdl.PrimitiveType.CastMode.TRUNCATED) f32 = pydsdl.FloatType(32, pydsdl.PrimitiveType.CastMode.SATURATED) bl = pydsdl.BooleanType(pydsdl.PrimitiveType.CastMode.SATURATED) # and template = ( '{{ i7 is zero_cost_primitive }} ' '{{ u32 is zero_cost_primitive }} ' '{{ f16 is zero_cost_primitive }} ' '{{ f32 is zero_cost_primitive }} ' '{{ bl is zero_cost_primitive }}' ) # then rendered = 'False True False True False' .. invisible-code-block: python options = {'target_endianness': 'little'} lctx = ( LanguageContextBuilder() .set_target_language("c") .set_target_language_configuration_override(Language.WKCV_LANGUAGE_OPTIONS, options) .create() ) jinja_filter_tester(is_zero_cost_primitive, template, rendered, lctx, i7=i7, u32=u32, f16=f16, f32=f32, bl=bl) # ensure unknown types given to test raise a TypeError try: jinja_filter_tester(is_zero_cost_primitive, template, 'True', lctx, u32=int(32)) assert False except TypeError: pass # big endian is never zero cost. options = {'target_endianness': 'big'} lctx = ( LanguageContextBuilder() .set_target_language("c") .set_target_language_configuration_override(Language.WKCV_LANGUAGE_OPTIONS, options) .create() ) jinja_filter_tester(is_zero_cost_primitive, template, 'False False False False False', lctx, i7=i7, u32=u32, f16=f16, f32=f32, bl=bl) """ if language.get_option("target_endianness") != "little": # We must explicitly target a little endian platform to get # zero cost ser/des. return False if isinstance(t, pydsdl.IntegerType): out = t.standard_bit_length assert isinstance(out, bool) return out if isinstance(t, pydsdl.FloatType): return t.bit_length in (32, 64) # float16 is excluded if isinstance(t, pydsdl.BooleanType): return False raise TypeError("Zero-cost predicate is not defined on " + type(t).__name__)
@template_language_filter(__name__) def filter_is_zero_cost_primitive(language: Language, t: pydsdl.PrimitiveType) -> str: """ Deprecated as a filter. Please use test version. .. invisible-code-block: python from nunavut.lang.c import filter_is_zero_cost_primitive, is_zero_cost_primitive import pydsdl u32 = pydsdl.UnsignedIntegerType(32, pydsdl.PrimitiveType.CastMode.TRUNCATED) .. code-block: python # Instead of this deprecated_template = '{{ u32 | is_zero_cost_primitive }}' # do this correct_template = '{{ u32 is zero_cost_primitive }}' .. invisible-code-block: python options = {'target_endianness': 'little'} lctx = ( LanguageContextBuilder() .set_target_language("c") .set_target_language_configuration_override(Language.WKCV_LANGUAGE_OPTIONS, options) .create() ) jinja_filter_tester(filter_is_zero_cost_primitive, deprecated_template, 'True', lctx, u32=u32) jinja_filter_tester(is_zero_cost_primitive, correct_template, 'True', lctx, u32=u32) """ return str(is_zero_cost_primitive(language, t))