1#
2# Copyright (c), 2018-2020, SISSA (International School for Advanced Studies).
3# All rights reserved.
4# This file is distributed under the terms of the MIT License.
5# See the file 'LICENSE' in the root directory of the present
6# distribution, or http://opensource.org/licenses/MIT.
7#
8# @author Davide Brunato <brunato@sissa.it>
9#
10import re
11import math
12from typing import Any, Optional, SupportsFloat, SupportsInt, Union, Type
13
14from ..helpers import collapse_white_spaces
15from .atomic_types import AtomicTypeMeta, AnyAtomicType
16
17
18class Float10(float, AnyAtomicType):
19    name = 'float'
20    xsd_version = '1.0'
21    pattern = re.compile(
22        r'^(?:[+-]?(?:[0-9]+(?:\.[0-9]*)?|\.[0-9]+)(?:[Ee][+-]?[0-9]+)? |[+-]?INF|NaN)$'
23    )
24
25    def __new__(cls, value: Union[str, SupportsFloat]) -> 'Float10':
26        if isinstance(value, str):
27            value = collapse_white_spaces(value)
28            if value in {'INF', '-INF', 'NaN'} or cls.xsd_version != '1.0' and value == '+INF':
29                pass
30            elif value.lower() in {'inf', '+inf', '-inf', 'nan',
31                                   'infinity', '+infinity', '-infinity'}:
32                raise ValueError('invalid value {!r} for xs:{}'.format(value, cls.name))
33
34        value = super().__new__(cls, value)
35        if value > 3.4028235E38:
36            return super().__new__(cls, 'INF')
37        elif value < -3.4028235E38:
38            return super().__new__(cls, '-INF')
39        elif -1e-37 < value < 1e-37:
40            return super().__new__(cls, -0.0 if str(value).startswith('-') else 0.0)
41        return value
42
43    def __hash__(self) -> int:
44        return super(Float10, self).__hash__()
45
46    def __eq__(self, other: object) -> bool:
47        if isinstance(other, self.__class__):
48            if super(Float10, self).__eq__(other):
49                return True
50            return math.isclose(self, other, rel_tol=1e-7, abs_tol=0.0)
51        return super(Float10, self).__eq__(other)
52
53    def __ne__(self, other: object) -> bool:
54        if isinstance(other, self.__class__):
55            if super(Float10, self).__eq__(other):
56                return False
57            return not math.isclose(self, other, rel_tol=1e-7, abs_tol=0.0)
58        return super(Float10, self).__ne__(other)
59
60    def __add__(self, other: object) -> Union[float, 'Float10', 'Float']:
61        if isinstance(other, (self.__class__, int)):
62            return self.__class__(super(Float10, self).__add__(other))
63        elif isinstance(other, float):
64            return super(Float10, self).__add__(other)
65        return NotImplemented
66
67    def __radd__(self, other: object) -> Union[float, 'Float10', 'Float']:
68        if isinstance(other, (self.__class__, int)):
69            return self.__class__(super(Float10, self).__radd__(other))
70        elif isinstance(other, float):
71            return super(Float10, self).__radd__(other)
72        return NotImplemented
73
74    def __sub__(self, other: object) -> Union[float, 'Float10', 'Float']:
75        if isinstance(other, (self.__class__, int)):
76            return self.__class__(super(Float10, self).__sub__(other))
77        elif isinstance(other, float):
78            return super(Float10, self).__sub__(other)
79        return NotImplemented
80
81    def __rsub__(self, other: object) -> Union[float, 'Float10', 'Float']:
82        if isinstance(other, (self.__class__, int)):
83            return self.__class__(super(Float10, self).__rsub__(other))
84        elif isinstance(other, float):
85            return super(Float10, self).__rsub__(other)
86        return NotImplemented
87
88    def __mul__(self, other: object) -> Union[float, 'Float10', 'Float']:
89        if isinstance(other, (self.__class__, int)):
90            return self.__class__(super(Float10, self).__mul__(other))
91        elif isinstance(other, float):
92            return super(Float10, self).__mul__(other)
93        return NotImplemented
94
95    def __rmul__(self, other: object) -> Union[float, 'Float10', 'Float']:
96        if isinstance(other, (self.__class__, int)):
97            return self.__class__(super(Float10, self).__rmul__(other))
98        elif isinstance(other, float):
99            return super(Float10, self).__rmul__(other)
100        return NotImplemented
101
102    def __truediv__(self, other: object) -> Union[float, 'Float10', 'Float']:
103        if isinstance(other, (self.__class__, int)):
104            return self.__class__(super(Float10, self).__truediv__(other))
105        elif isinstance(other, float):
106            return super(Float10, self).__truediv__(other)
107        return NotImplemented
108
109    def __rtruediv__(self, other: object) -> Union[float, 'Float10', 'Float']:
110        if isinstance(other, (self.__class__, int)):
111            return self.__class__(super(Float10, self).__rtruediv__(other))
112        elif isinstance(other, float):
113            return super(Float10, self).__rtruediv__(other)
114        return NotImplemented
115
116    def __mod__(self, other: object) -> Union[float, 'Float10', 'Float']:
117        if isinstance(other, (self.__class__, int)):
118            return self.__class__(super(Float10, self).__mod__(other))
119        elif isinstance(other, float):
120            return super(Float10, self).__mod__(other)
121        return NotImplemented
122
123    def __rmod__(self, other: object) -> Union[float, 'Float10', 'Float']:
124        if isinstance(other, (self.__class__, int)):
125            return self.__class__(super(Float10, self).__rmod__(other))
126        elif isinstance(other, float):
127            return super(Float10, self).__rmod__(other)
128        return NotImplemented
129
130    def __abs__(self) -> Union['Float10', 'Float']:
131        return self.__class__(super(Float10, self).__abs__())
132
133
134class Float(Float10):
135    name = 'float'
136    xsd_version = '1.1'
137
138
139class Integer(int, metaclass=AtomicTypeMeta):
140    """A wrapper for emulating xs:integer and limited integer types."""
141    name = 'integer'
142    pattern = re.compile(r'^[\-+]?[0-9]+$')
143    lower_bound: Optional[int] = None
144    higher_bound: Optional[int] = None
145
146    def __init__(self, value: Union[str, SupportsInt]) -> None:
147        if self.lower_bound is not None and self < self.lower_bound:
148            raise ValueError("value {} is too low for {!r}".format(value, self.__class__))
149        elif self.higher_bound is not None and self >= self.higher_bound:
150            raise ValueError("value {} is too high for {!r}".format(value, self.__class__))
151        super(Integer, self).__init__()
152
153    @classmethod
154    def __subclasshook__(cls, subclass: Type[Any]) -> bool:
155        if cls is Integer:
156            return issubclass(subclass, int) and not issubclass(subclass, bool)
157        return NotImplemented
158
159    @classmethod
160    def validate(cls, value: object) -> None:
161        if isinstance(value, cls):
162            return
163        elif isinstance(value, str):
164            if cls.pattern.match(value) is None:
165                raise cls.invalid_value(value)
166        else:
167            raise cls.invalid_type(value)
168
169
170class NonPositiveInteger(Integer):
171    name = 'nonPositiveInteger'
172    lower_bound, higher_bound = None, 1
173
174
175class NegativeInteger(NonPositiveInteger):
176    name = 'negativeInteger'
177    lower_bound, higher_bound = None, 0
178
179
180class Long(Integer):
181    name = 'long'
182    lower_bound, higher_bound = -2**63, 2**63
183
184
185class Int(Long):
186    name = 'int'
187    lower_bound, higher_bound = -2**31, 2**31
188
189
190class Short(Int):
191    name = 'short'
192    lower_bound, higher_bound = -2**15, 2**15
193
194
195class Byte(Short):
196    name = 'byte'
197    lower_bound, higher_bound = -2**7, 2**7
198
199
200class NonNegativeInteger(Integer):
201    name = 'nonNegativeInteger'
202    lower_bound = 0
203    higher_bound: Optional[int] = None
204
205
206class PositiveInteger(NonNegativeInteger):
207    name = 'positiveInteger'
208    lower_bound, higher_bound = 1, None
209
210
211class UnsignedLong(NonNegativeInteger):
212    name = 'unsignedLong'
213    lower_bound, higher_bound = 0, 2**64
214
215
216class UnsignedInt(UnsignedLong):
217    name = 'unsignedInt'
218    lower_bound, higher_bound = 0, 2**32
219
220
221class UnsignedShort(UnsignedInt):
222    name = 'unsignedShort'
223    lower_bound, higher_bound = 0, 2**16
224
225
226class UnsignedByte(UnsignedShort):
227    name = 'unsignedByte'
228    lower_bound, higher_bound = 0, 2**8
229