fix(core): do not subclass `range`

micropython on real hw dislikes it for some reason

also it's completely unnecessary
pull/1346/head
matejcik 4 years ago
parent be9fcf7525
commit e6a1bf840f

@ -12,6 +12,7 @@ if False:
Any, Any,
Callable, Callable,
Collection, Collection,
Container,
Iterable, Iterable,
List, List,
Sequence, Sequence,
@ -33,25 +34,17 @@ if False:
... ...
class _FastInclusiveRange(range): class Interval:
"""Inclusive range with a fast membership test, suitable for PathSchema use. """Helper for testing membership in an interval."""
Micropython's `range` does not implement the `__contains__` method. This makes
checking whether `x in range(BIG_NUMBER)` slow. This class fixes the problem.
In addition, convenience modifications have been made:
* both `min` and `max` belong to the range (so `stop == max + 1`).
* both `min` and `max` must be set, `step` is not allowed (we don't need it and it
would make the `__contains__` method more complex)
"""
def __init__(self, min: int, max: int) -> None: def __init__(self, min: int, max: int) -> None:
super().__init__(min, max + 1) self.min = min
self.max = max
def __contains__(self, x: object) -> bool: def __contains__(self, x: object) -> bool:
if not isinstance(x, int): if not isinstance(x, int):
return False return False
return self.start <= x < self.stop return self.min <= x <= self.max
class PathSchema: class PathSchema:
@ -107,9 +100,9 @@ class PathSchema:
} }
WILDCARD_RANGES = { WILDCARD_RANGES = {
"*": _FastInclusiveRange(0, HARDENED - 1), "*": Interval(0, HARDENED - 1),
"*'": _FastInclusiveRange(HARDENED, 0xFFFF_FFFF), "*'": Interval(HARDENED, 0xFFFF_FFFF),
"**": _FastInclusiveRange(0, 0xFFFF_FFFF), "**": Interval(0, 0xFFFF_FFFF),
} }
def __init__(self, pattern: str, slip44_id: Union[int, Iterable[int]]) -> None: def __init__(self, pattern: str, slip44_id: Union[int, Iterable[int]]) -> None:
@ -120,8 +113,8 @@ class PathSchema:
if isinstance(slip44_id, int): if isinstance(slip44_id, int):
slip44_id = (slip44_id,) slip44_id = (slip44_id,)
self.schema: List[Collection[int]] = [] self.schema: List[Container[int]] = []
self.trailing_components: Collection[int] = () self.trailing_components: Container[int] = ()
for component in components: for component in components:
if component in self.WILDCARD_RANGES: if component in self.WILDCARD_RANGES:
@ -151,7 +144,7 @@ class PathSchema:
if "-" in component: if "-" in component:
# parse as a range # parse as a range
a, b = [parse(s) for s in component.split("-", 1)] a, b = [parse(s) for s in component.split("-", 1)]
self.schema.append(_FastInclusiveRange(a, b)) self.schema.append(Interval(a, b))
elif "," in component: elif "," in component:
# parse as a list of values # parse as a list of values
@ -193,18 +186,23 @@ class PathSchema:
return item ^ (item & HARDENED) return item ^ (item & HARDENED)
for component in self.schema: for component in self.schema:
if isinstance(component, range): if isinstance(component, Interval):
a, b = component.start, component.stop - 1 a, b = component.min, component.max
components.append( components.append(
"[{}-{}]{}".format( "[{}-{}]{}".format(
unharden(a), unharden(b), "'" if a & HARDENED else "" unharden(a), unharden(b), "'" if a & HARDENED else ""
) )
) )
else: else:
component_str = ",".join(str(unharden(i)) for i in component) # mypy thinks component is a Contanier but we're using it as a Collection.
if len(component) > 1: # Which in practice it is, the only non-Collection is Interval.
# But we're not going to introduce an additional type requirement
# for the sake of __repr__ that doesn't exist in production anyway
collection: Collection[int] = component # type: ignore
component_str = ",".join(str(unharden(i)) for i in collection)
if len(collection) > 1:
component_str = "[" + component_str + "]" component_str = "[" + component_str + "]"
if next(iter(component)) & HARDENED: if next(iter(collection)) & HARDENED:
component_str += "'" component_str += "'"
components.append(component_str) components.append(component_str)

@ -41,8 +41,8 @@ class TestPathSchemas(unittest.TestCase):
def assertEqualSchema(self, schema_a, schema_b): def assertEqualSchema(self, schema_a, schema_b):
def is_equal(a, b): def is_equal(a, b):
if isinstance(a, range) and isinstance(b, range): if isinstance(a, Interval) and isinstance(b, Interval):
return a.start == b.start and a.step == b.step and a.stop == b.stop return a.min == b.min and a.max == b.max
return set(a) == set(b) return set(a) == set(b)
ensure( ensure(

Loading…
Cancel
Save