Source code for remerkleable.basic

from typing import Any, TypeVar, Type
from remerkleable.core import BasicView, View, ObjType, ObjParseException

V = TypeVar('V', bound=View)


# Not returning "NotImplemented" like regular operators,
# it's completely invalid, do not let the interpreter resort to the other operation hand.
[docs]class OperationNotSupported(Exception): pass
BoolV = TypeVar('BoolV', bound="boolean")
[docs]class boolean(int, BasicView):
[docs] def encode_bytes(self) -> bytes: return b"\x01" if self else b"\x00"
def __new__(cls, value: int): # int value, but can be any subclass of int (bool, Bit, Bool, etc...) if value < 0 or value > 1: raise ValueError(f"value {value} out of bounds for bit") return super().__new__(cls, value) # type: ignore def __add__(self, other): raise OperationNotSupported(f"cannot add bool ({self} + {other})") def __sub__(self, other): raise OperationNotSupported(f"cannot sub bool ({self} - {other})") def __mul__(self, other): raise OperationNotSupported(f"cannot mul bool ({self} * {other})") def __floordiv__(self, other): # Better known as "//" raise OperationNotSupported(f"cannot floordiv bool ({self} // {other})") def __truediv__(self, other): raise OperationNotSupported(f"cannot truediv bool ({self} / {other})") def __bool__(self): return self > 0
[docs] @classmethod def coerce_view(cls: Type[BoolV], v: Any) -> BoolV: return cls(v)
[docs] @classmethod def type_byte_length(cls) -> int: return 1
[docs] @classmethod def decode_bytes(cls: Type[BoolV], bytez: bytes) -> BoolV: return cls(bytez != b"\x00")
[docs] @classmethod def from_obj(cls: Type[BoolV], obj: ObjType) -> BoolV: if not isinstance(obj, bool): raise ObjParseException(f"obj '{obj}' is not a bool") return cls(obj)
[docs] def to_obj(self) -> ObjType: return bool(self)
[docs] @classmethod def type_repr(cls) -> str: return "boolean"
T = TypeVar('T', bound="uint") W = TypeVar('W', bound=int)
[docs]class uint(int, BasicView): def __new__(cls, value: int): if value < 0: raise ValueError(f"unsigned type {cls} must not be negative") byte_len = cls.type_byte_length() if value.bit_length() > (byte_len << 3): raise ValueError(f"value out of bounds for {cls}") return super().__new__(cls, value) # type: ignore def __add__(self: T, other: int) -> T: return self.__class__(super().__add__(self.__class__.coerce_view(other))) def __radd__(self: T, other: int) -> T: return self.__add__(other) def __sub__(self: T, other: int) -> T: return self.__class__(super().__sub__(self.__class__.coerce_view(other))) def __rsub__(self: T, other: int) -> T: return self.__class__(self.__class__.coerce_view(other).__sub__(self)) def __mul__(self, other): if not isinstance(other, int): return super().__mul__(other) return self.__class__(super().__mul__(self.__class__.coerce_view(other))) def __rmul__(self, other): return self.__mul__(other) def __mod__(self: T, other: int) -> T: return self.__class__(super().__mod__(self.__class__.coerce_view(other))) def __rmod__(self: T, other: int) -> T: return self.__class__(self.__class__.coerce_view(other).__mod__(self)) def __floordiv__(self: T, other: int) -> T: # Better known as "//" return self.__class__(super().__floordiv__(self.__class__.coerce_view(other))) def __rfloordiv__(self: T, other: int) -> T: return self.__class__(self.__class__.coerce_view(other).__floordiv__(self)) def __truediv__(self, other): raise OperationNotSupported(f"non-integer division '{self} / {other}' " f"is not valid for {self.__class__.type_repr()} left hand type") def __rtruediv__(self, other): raise OperationNotSupported(f"non-integer division '{other} / {self}' " f"is not valid for {self.__class__.type_repr()} right hand type") def __pow__(self: T, other: int, modulo=None) -> T: return self.__class__(super().__pow__(other, modulo)) # TODO: stricter argument checks? def __rpow__(self: T, other, modulo=None) -> T: return self.__class__(super().__rpow__(other, modulo)) # TODO: see __pow__ def __lshift__(self: T, other: int) -> T: """Left bitshift clips bits at uint boundary""" mask = (1 << (self.type_byte_length() << 3)) - 1 return self.__class__(super().__lshift__(int(other)) & mask) def __rlshift__(self, other: W) -> W: if not isinstance(other, uint): raise ValueError(f"{other} << {self} through __rlshift__ is not supported, " f"left operand {other} must be a uint type with __lshift__") return other.__lshift__(self) # type: ignore def __rshift__(self: T, other: int) -> T: return self.__class__(super().__rshift__(int(other))) def __rrshift__(self, other: W) -> W: if not isinstance(other, uint): raise ValueError(f"{other} >> {self} through __rrshift__ is not supported, " f"left operand {other} must be a uint type with __rshift__") return other.__rshift__(self) # type: ignore def __and__(self: T, other: int) -> T: return self.__class__(super().__and__(self.__class__.coerce_view(other))) def __rand__(self: T, other: int) -> T: return self.__and__(other) def __xor__(self: T, other: int) -> T: return self.__class__(super().__xor__(self.__class__.coerce_view(other))) def __rxor__(self: T, other: int) -> T: return self.__xor__(other) def __or__(self: T, other: int) -> T: return self.__class__(super().__or__(self.__class__.coerce_view(other))) def __ror__(self: T, other: int) -> T: return self.__or__(other) def __neg__(self): raise OperationNotSupported("Cannot make uint type negative! If intentional, cast to signed int first.") def __invert__(self: T) -> T: mask = (1 << (self.type_byte_length() << 3)) - 1 return self.__xor__(mask) def __pos__(self: T) -> T: return self def __abs__(self: T) -> T: return self # __coerce__ is avoided to utilize explicit type hinting and special case the edge-cases for unsigned int safety
[docs] @classmethod def coerce_view(cls: Type[T], v: Any) -> T: if isinstance(v, uint) and cls.type_byte_length() != v.__class__.type_byte_length(): raise ValueError("value must have equal byte length to coerce it") if isinstance(v, bytes): return cls.decode_bytes(v) return cls(v)
[docs] @classmethod def decode_bytes(cls: Type[T], bytez: bytes) -> T: return cls(int.from_bytes(bytez, byteorder='little'))
[docs] def encode_bytes(self) -> bytes: return self.to_bytes(length=self.__class__.type_byte_length(), byteorder='little')
[docs] @classmethod def from_obj(cls: Type[T], obj: ObjType) -> T: if not isinstance(obj, (int, str)): raise ObjParseException(f"obj '{obj}' is not an int or str") if isinstance(obj, str): if obj.startswith('0x'): return cls.decode_bytes(bytes.fromhex(obj[2:])) obj = int(obj) return cls(obj)
[docs] def to_obj(self) -> ObjType: return int(self)
[docs] @classmethod def type_repr(cls) -> str: return f"uint{cls.type_byte_length()*8}"
[docs]class uint8(uint):
[docs] @classmethod def type_byte_length(cls) -> int: return 1
[docs]class uint16(uint):
[docs] @classmethod def type_byte_length(cls) -> int: return 2
[docs]class uint32(uint):
[docs] @classmethod def type_byte_length(cls) -> int: return 4
[docs]class uint64(uint):
[docs] @classmethod def type_byte_length(cls) -> int: return 8
# JSON encoder should be able to handle uint64, converting it to a string if necessary. # no "to_obj" here.
[docs]class uint128(uint):
[docs] @classmethod def type_byte_length(cls) -> int: return 16
[docs] def to_obj(self) -> ObjType: return "0x" + self.encode_bytes().hex()
[docs]class uint256(uint):
[docs] @classmethod def type_byte_length(cls) -> int: return 32
[docs] def to_obj(self) -> ObjType: return "0x" + self.encode_bytes().hex()
[docs]class bit(boolean): pass
[docs]class byte(uint8): pass