-
-
Notifications
You must be signed in to change notification settings - Fork 11
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Refactored _expression into a package
- Loading branch information
1 parent
dbb7547
commit 28e0fe8
Showing
15 changed files
with
897 additions
and
822 deletions.
There are no files selected for viewing
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
# | ||
# Copyright (C) 2018-2019 UAVCAN Development Team <uavcan.org> | ||
# This software is distributed under the terms of the MIT License. | ||
# | ||
|
||
from ._any import Any | ||
from ._any import UndefinedOperatorError, UndefinedAttributeError, InvalidOperandError | ||
|
||
from ._primitive import Primitive, Rational, Boolean, String | ||
|
||
from ._container import Container, Set | ||
|
||
from ._operator import OperatorOutput, BinaryOperator, AttributeOperator | ||
from ._operator import positive, negative | ||
from ._operator import logical_not, logical_or, logical_and | ||
from ._operator import equal, not_equal, less_or_equal, greater_or_equal, less, greater | ||
from ._operator import bitwise_and, bitwise_xor, bitwise_or | ||
from ._operator import add, subtract, multiply, divide, modulo, power | ||
from ._operator import attribute |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
# | ||
# Copyright (C) 2018-2019 UAVCAN Development Team <uavcan.org> | ||
# This software is distributed under the terms of the MIT License. | ||
# | ||
|
||
import abc | ||
from .. import _error | ||
|
||
|
||
class InvalidOperandError(_error.InvalidDefinitionError): | ||
pass | ||
|
||
|
||
class UndefinedOperatorError(InvalidOperandError): | ||
"""Thrown when there is no matching operator for the supplied arguments.""" | ||
def __init__(self) -> None: | ||
super(UndefinedOperatorError, self).__init__('The requested operator is not defined for the provided arguments') | ||
|
||
|
||
class UndefinedAttributeError(InvalidOperandError): | ||
"""Thrown when the requested attribute does not exist.""" | ||
def __init__(self) -> None: | ||
super(UndefinedAttributeError, self).__init__('Invalid attribute name') | ||
|
||
|
||
class Any(abc.ABC): | ||
""" | ||
This abstract class represents an arbitrary intrinsic DSDL expression value. | ||
""" | ||
# This attribute must be specified in the derived classes. | ||
# It contains the name of the data type implemented by the class. | ||
TYPE_NAME = None # type: str | ||
|
||
@abc.abstractmethod | ||
def __hash__(self) -> int: | ||
raise NotImplementedError # pragma: no cover | ||
|
||
@abc.abstractmethod | ||
def __eq__(self, other: object) -> bool: | ||
raise NotImplementedError # pragma: no cover | ||
|
||
@abc.abstractmethod | ||
def __str__(self) -> str: | ||
"""Must return a DSDL spec-compatible textual representation of the contained value suitable for printing.""" | ||
raise NotImplementedError # pragma: no cover | ||
|
||
def __repr__(self) -> str: | ||
return self.TYPE_NAME + '(' + str(self) + ')' | ||
|
||
# | ||
# Unary operators. | ||
# | ||
def _logical_not(self) -> 'Boolean': raise UndefinedOperatorError | ||
|
||
def _positive(self) -> 'Any': raise UndefinedOperatorError | ||
|
||
def _negative(self) -> 'Any': raise UndefinedOperatorError | ||
|
||
# | ||
# Binary operators. | ||
# The types of the operators defined here must match the specification. | ||
# Make sure to use least generic types in the derived classes - Python allows covariant return types. | ||
# | ||
def _logical_or(self, right: 'Any') -> 'Boolean': raise UndefinedOperatorError | ||
def _logical_and(self, right: 'Any') -> 'Boolean': raise UndefinedOperatorError | ||
|
||
def _equal(self, right: 'Any') -> 'Boolean': raise UndefinedOperatorError # pragma: no branch | ||
def _less_or_equal(self, right: 'Any') -> 'Boolean': raise UndefinedOperatorError | ||
def _greater_or_equal(self, right: 'Any') -> 'Boolean': raise UndefinedOperatorError | ||
def _less(self, right: 'Any') -> 'Boolean': raise UndefinedOperatorError | ||
def _greater(self, right: 'Any') -> 'Boolean': raise UndefinedOperatorError | ||
|
||
def _bitwise_or(self, right: 'Any') -> 'Any': raise UndefinedOperatorError | ||
def _bitwise_or_right(self, left: 'Any') -> 'Any': raise UndefinedOperatorError | ||
|
||
def _bitwise_xor(self, right: 'Any') -> 'Any': raise UndefinedOperatorError | ||
def _bitwise_xor_right(self, left: 'Any') -> 'Any': raise UndefinedOperatorError | ||
|
||
def _bitwise_and(self, right: 'Any') -> 'Any': raise UndefinedOperatorError | ||
def _bitwise_and_right(self, left: 'Any') -> 'Any': raise UndefinedOperatorError | ||
|
||
def _add(self, right: 'Any') -> 'Any': raise UndefinedOperatorError | ||
def _add_right(self, left: 'Any') -> 'Any': raise UndefinedOperatorError | ||
|
||
def _subtract(self, right: 'Any') -> 'Any': raise UndefinedOperatorError | ||
def _subtract_right(self, left: 'Any') -> 'Any': raise UndefinedOperatorError | ||
|
||
def _multiply(self, right: 'Any') -> 'Any': raise UndefinedOperatorError | ||
def _multiply_right(self, left: 'Any') -> 'Any': raise UndefinedOperatorError | ||
|
||
def _divide(self, right: 'Any') -> 'Any': raise UndefinedOperatorError | ||
def _divide_right(self, left: 'Any') -> 'Any': raise UndefinedOperatorError | ||
|
||
def _modulo(self, right: 'Any') -> 'Any': raise UndefinedOperatorError | ||
def _modulo_right(self, left: 'Any') -> 'Any': raise UndefinedOperatorError | ||
|
||
def _power(self, right: 'Any') -> 'Any': raise UndefinedOperatorError | ||
def _power_right(self, left: 'Any') -> 'Any': raise UndefinedOperatorError | ||
|
||
# | ||
# Attribute access operator. It is a binary operator as well, but its semantics is slightly different. | ||
# Implementations must invoke super()._attribute() when they encounter an unknown attribute, to allow | ||
# the parent classes to handle the requested attribute as a fallback option. | ||
# | ||
def _attribute(self, name: 'String') -> 'Any': raise UndefinedAttributeError | ||
|
||
|
||
# This import must be located at the bottom to break the circular dependency in the type annotations above. | ||
# We must import specific names as opposed to the whole module because the latter breaks MyPy. | ||
from ._primitive import Boolean, String # noqa |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,237 @@ | ||
# | ||
# Copyright (C) 2018-2019 UAVCAN Development Team <uavcan.org> | ||
# This software is distributed under the terms of the MIT License. | ||
# | ||
|
||
import abc | ||
import typing | ||
import functools | ||
from . import _any, _primitive, _operator | ||
|
||
|
||
_O = typing.TypeVar('_O') | ||
|
||
|
||
# noinspection PyAbstractClass | ||
class Container(_any.Any): | ||
@property | ||
@abc.abstractmethod | ||
def element_type(self) -> typing.Type[_any.Any]: | ||
raise NotImplementedError # pragma: no cover | ||
|
||
@abc.abstractmethod | ||
def __iter__(self) -> typing.Iterator[typing.Any]: | ||
raise NotImplementedError # pragma: no cover | ||
|
||
|
||
class Set(Container): | ||
TYPE_NAME = 'set' | ||
|
||
# noinspection PyProtectedMember | ||
class _Decorator: | ||
@staticmethod | ||
def homotypic_binary_operator(inferior: typing.Callable[['Set', 'Set'], _O]) \ | ||
-> typing.Callable[['Set', 'Set'], _O]: | ||
def wrapper(self: 'Set', other: 'Set') -> _O: | ||
assert isinstance(self, Set) and isinstance(other, Set) | ||
if self.element_type == other.element_type: | ||
return inferior(self, other) | ||
else: | ||
raise _any.InvalidOperandError('The requested binary operator is defined only for sets ' | ||
'that share the same element type. The different types are: %r, %r' % | ||
(self.element_type.TYPE_NAME, other.element_type.TYPE_NAME)) | ||
return wrapper | ||
|
||
def __init__(self, elements: typing.Iterable[_any.Any]): | ||
list_of_elements = list(elements) # type: typing.List[_any.Any] | ||
del elements | ||
if len(list_of_elements) < 1: | ||
raise _any.InvalidOperandError('Zero-length sets are currently not permitted because ' | ||
'of associated type deduction issues. This may change later.') | ||
|
||
element_types = set(map(type, list_of_elements)) | ||
if len(element_types) != 1: | ||
# This also weeds out covariant sets, although our barbie-size type system is unaware of that. | ||
raise _any.InvalidOperandError('Heterogeneous sets are not permitted') | ||
|
||
# noinspection PyTypeChecker | ||
self._element_type = list(element_types)[0] # type: typing.Type[_any.Any] | ||
self._value = frozenset(list_of_elements) # type: typing.FrozenSet[_any.Any] | ||
|
||
if not issubclass(self._element_type, _any.Any): | ||
raise ValueError('Invalid element type: %r' % self._element_type) | ||
|
||
def __iter__(self) -> typing.Iterator[typing.Any]: | ||
return iter(self._value) | ||
|
||
@property | ||
def element_type(self) -> typing.Type[_any.Any]: | ||
return self._element_type | ||
|
||
def __hash__(self) -> int: | ||
return hash(self._value) | ||
|
||
def __eq__(self, other: object) -> bool: | ||
if isinstance(other, Set): | ||
return self._value == other._value | ||
else: | ||
return NotImplemented | ||
|
||
def __str__(self) -> str: | ||
return '{%s}' % ', '.join(map(str, self._value)) # This is recursive. | ||
|
||
# | ||
# Set algebra implementation. | ||
# | ||
@_Decorator.homotypic_binary_operator | ||
def _is_equal_to(self, right: 'Set') -> bool: | ||
return self._value == right._value | ||
|
||
@_Decorator.homotypic_binary_operator | ||
def _is_superset_of(self, right: 'Set') -> bool: | ||
return self._value.issuperset(right._value) | ||
|
||
@_Decorator.homotypic_binary_operator | ||
def _is_subset_of(self, right: 'Set') -> bool: | ||
return self._value.issubset(right._value) | ||
|
||
@_Decorator.homotypic_binary_operator | ||
def _is_proper_superset_of(self, right: 'Set') -> bool: | ||
return self._is_superset_of(right) and not self._is_equal_to(right) | ||
|
||
@_Decorator.homotypic_binary_operator | ||
def _is_proper_subset_of(self, right: 'Set') -> bool: | ||
return self._is_subset_of(right) and not self._is_equal_to(right) | ||
|
||
@_Decorator.homotypic_binary_operator | ||
def _create_union_with(self, right: 'Set') -> 'Set': | ||
return Set(self._value.union(right._value)) | ||
|
||
@_Decorator.homotypic_binary_operator | ||
def _create_intersection_with(self, right: 'Set') -> 'Set': | ||
return Set(self._value.intersection(right._value)) | ||
|
||
@_Decorator.homotypic_binary_operator | ||
def _create_disjunctive_union_with(self, right: 'Set') -> 'Set': | ||
return Set(self._value.symmetric_difference(right._value)) | ||
|
||
# | ||
# Set comparison. | ||
# | ||
def _equal(self, right: _any.Any) -> _primitive.Boolean: | ||
if isinstance(right, Set): | ||
return _primitive.Boolean(self._is_equal_to(right)) | ||
else: | ||
raise _any.UndefinedOperatorError | ||
|
||
def _less_or_equal(self, right: _any.Any) -> _primitive.Boolean: | ||
if isinstance(right, Set): | ||
return _primitive.Boolean(self._is_subset_of(right)) | ||
else: | ||
raise _any.UndefinedOperatorError | ||
|
||
def _greater_or_equal(self, right: _any.Any) -> _primitive.Boolean: | ||
if isinstance(right, Set): | ||
return _primitive.Boolean(self._is_superset_of(right)) | ||
else: | ||
raise _any.UndefinedOperatorError | ||
|
||
def _less(self, right: _any.Any) -> _primitive.Boolean: | ||
if isinstance(right, Set): | ||
return _primitive.Boolean(self._is_proper_subset_of(right)) | ||
else: | ||
raise _any.UndefinedOperatorError | ||
|
||
def _greater(self, right: _any.Any) -> _primitive.Boolean: | ||
if isinstance(right, Set): | ||
return _primitive.Boolean(self._is_proper_superset_of(right)) | ||
else: | ||
raise _any.UndefinedOperatorError | ||
|
||
# | ||
# Set algebra operators that yield a new set. | ||
# | ||
def _bitwise_or(self, right: _any.Any) -> 'Set': | ||
if isinstance(right, Set): | ||
return self._create_union_with(right) | ||
else: | ||
raise _any.UndefinedOperatorError | ||
|
||
def _bitwise_xor(self, right: _any.Any) -> 'Set': | ||
if isinstance(right, Set): | ||
return self._create_disjunctive_union_with(right) | ||
else: | ||
raise _any.UndefinedOperatorError | ||
|
||
def _bitwise_and(self, right: _any.Any) -> 'Set': | ||
if isinstance(right, Set): | ||
return self._create_intersection_with(right) | ||
else: | ||
raise _any.UndefinedOperatorError | ||
|
||
# | ||
# Elementwise application. | ||
# https://stackoverflow.com/questions/55148139/referring-to-a-pure-virtual-method | ||
# | ||
def _elementwise(self, | ||
impl: typing.Callable[[_any.Any, _any.Any], _any.Any], | ||
other: _any.Any, | ||
swap: bool = False) -> 'Set': | ||
if not isinstance(other, Set): | ||
return Set((impl(other, x) if swap else impl(x, other)) for x in self) | ||
else: | ||
raise _any.UndefinedOperatorError | ||
|
||
def _add(self, right: _any.Any) -> 'Set': | ||
return self._elementwise(_operator.add, right) | ||
|
||
def _add_right(self, left: _any.Any) -> 'Set': | ||
return self._elementwise(_operator.add, left, swap=True) | ||
|
||
def _subtract(self, right: _any.Any) -> 'Set': | ||
return self._elementwise(_operator.subtract, right) | ||
|
||
def _subtract_right(self, left: _any.Any) -> 'Set': | ||
return self._elementwise(_operator.subtract, left, swap=True) | ||
|
||
def _multiply(self, right: _any.Any) -> 'Set': | ||
return self._elementwise(_operator.multiply, right) | ||
|
||
def _multiply_right(self, left: _any.Any) -> 'Set': | ||
return self._elementwise(_operator.multiply, left, swap=True) | ||
|
||
def _divide(self, right: _any.Any) -> 'Set': | ||
return self._elementwise(_operator.divide, right) | ||
|
||
def _divide_right(self, left: _any.Any) -> 'Set': | ||
return self._elementwise(_operator.divide, left, swap=True) | ||
|
||
def _modulo(self, right: _any.Any) -> 'Set': | ||
return self._elementwise(_operator.modulo, right) | ||
|
||
def _modulo_right(self, left: _any.Any) -> 'Set': | ||
return self._elementwise(_operator.modulo, left, swap=True) | ||
|
||
def _power(self, right: _any.Any) -> 'Set': | ||
return self._elementwise(_operator.power, right) | ||
|
||
def _power_right(self, left: _any.Any) -> 'Set': | ||
return self._elementwise(_operator.power, left, swap=True) | ||
|
||
# | ||
# Attributes | ||
# | ||
def _attribute(self, name: '_primitive.String') -> _any.Any: | ||
if name.native_value == 'min': | ||
out = functools.reduce(lambda a, b: a if _operator.less(a, b) else b, self) | ||
assert isinstance(out, self.element_type) | ||
elif name.native_value == 'max': | ||
out = functools.reduce(lambda a, b: a if _operator.greater(a, b) else b, self) | ||
assert isinstance(out, self.element_type) | ||
elif name.native_value == 'count': # "size" and "length" can be ambiguous, "cardinality" is long | ||
out = _primitive.Rational(len(self._value)) | ||
else: | ||
out = super(Set, self)._attribute(name) # Hand over up the inheritance chain, this is important | ||
|
||
assert isinstance(out, _any.Any) | ||
return out |
Oops, something went wrong.