from __future__ import annotations from tibs import Tibs, Mutibs from bitstring.exceptions import CreationError from typing import Union, Iterable, Optional, overload, Iterator, Any from bitstring.helpers import offset_slice_indices_lsb0 class ConstBitStore: """A light wrapper around tibs.Tibs that does the LSB0 stuff""" __slots__ = ('_bits',) def __init__(self, initializer: Union[Tibs, None] = None) -> None: if initializer is not None: self._bits = initializer else: self._bits = Tibs() @classmethod def join(cls, bitstores: Iterable[ConstBitStore], /) -> ConstBitStore: x = super().__new__(cls) x._bits = Tibs.from_joined(b._bits for b in bitstores) return x @classmethod def from_zeros(cls, i: int): x = super().__new__(cls) x._bits = Tibs.from_zeros(i) return x @classmethod def from_tibs(cls, tb: Tibs): x = super().__new__(cls) x._bits = tb return x @classmethod def from_bytes(cls, b: Union[bytes, bytearray, memoryview], /) -> ConstBitStore: x = super().__new__(cls) x._bits = Tibs.from_bytes(b) return x @classmethod def frombuffer(cls, buffer, /, length: Optional[int] = None) -> ConstBitStore: x = super().__new__(cls) # TODO: tibs needs a Tibs.from_buffer method. x._bits = Tibs.from_bytes(bytes(buffer)) if length is not None: if length < 0: raise CreationError("Can't create bitstring with a negative length.") if length > len(x._bits): raise CreationError( f"Can't create bitstring with a length of {length} from {len(x._bits)} bits of data.") return x.getslice(0, length) if length is not None else x @classmethod def from_bin(cls, s: str) -> ConstBitStore: x = super().__new__(cls) x._bits = Tibs.from_bin(s) return x def set(self, value, pos) -> None: self._bits.set(value, pos) @staticmethod def using_rust_core() -> bool: return True def tobitarray(self): raise TypeError("tobitarray() is not available when using the Rust core option.") def to_bytes(self, pad_at_end: bool = True) -> bytes: excess_bits = len(self._bits) % 8 if excess_bits != 0: # Pad with zeros to make full bytes if pad_at_end: padded_bits = self._bits.to_mutibs().extend(Mutibs.from_zeros(8 - excess_bits)) else: padded_bits = self._bits.to_mutibs().extend_left(Mutibs.from_zeros(8 - excess_bits)) return padded_bits.to_bytes() return self._bits.to_bytes() def to_u(self) -> int: if len(self) > 128: return int.from_bytes(self.to_bytes(pad_at_end=False), byteorder="big", signed=False) try: return self._bits.to_u() except OverflowError as e: raise ValueError(e) def to_i(self) -> int: if len(self) > 128: return int.from_bytes(self.to_bytes(pad_at_end=False), byteorder="big", signed=True) try: return self._bits.to_i() except OverflowError as e: raise ValueError(e) def to_hex(self) -> str: return self._bits.to_hex() def to_bin(self) -> str: return self._bits.to_bin() def to_oct(self) -> str: return self._bits.to_oct() def __add__(self, other: ConstBitStore, /) -> ConstBitStore: newbits = self._bits + other._bits return ConstBitStore.from_tibs(newbits) def __eq__(self, other: Any, /) -> bool: return self._bits == other._bits def __and__(self, other: ConstBitStore, /) -> ConstBitStore: return ConstBitStore.from_tibs(self._bits & other._bits) def __or__(self, other: ConstBitStore, /) -> ConstBitStore: return ConstBitStore.from_tibs(self._bits | other._bits) def __xor__(self, other: ConstBitStore, /) -> ConstBitStore: return ConstBitStore.from_tibs(self._bits ^ other._bits) def __invert__(self) -> ConstBitStore: return ConstBitStore.from_tibs(~self._bits) def find(self, bs: ConstBitStore, start: int, end: int, bytealigned: bool = False) -> int | None: assert start >= 0 return self._bits.find(bs._bits, start, end, byte_aligned=bytealigned) def rfind(self, bs: ConstBitStore, start: int, end: int, bytealigned: bool = False) -> int | None: assert start >= 0 return self._bits.rfind(bs._bits, start, end, byte_aligned=bytealigned) def findall_msb0(self, bs: ConstBitStore, start: int, end: int, bytealigned: bool = False) -> Iterator[int]: x = self._bits for p in x.find_all(bs._bits, start=start, end=end, byte_aligned=bytealigned): yield p def rfindall_msb0(self, bs: ConstBitStore, start: int, end: int, bytealigned: bool = False) -> Iterator[int]: x = self._bits for p in x.rfind_all(bs._bits, start=start, end=end, byte_aligned=bytealigned): yield p def count(self, value, /) -> int: return self._bits.count(value) def __iter__(self) -> Iterable[bool]: length = len(self) for i in range(length): yield self.getindex(i) def _mutable_copy(self) -> MutableBitStore: """Always creates a copy, even if instance is immutable.""" return MutableBitStore.from_mutibs(self._bits.to_mutibs()) def copy(self) -> ConstBitStore: return self if isinstance(self._bits, Tibs) else self._mutable_copy() def __getitem__(self, item: Union[int, slice], /) -> Union[int, ConstBitStore]: # Use getindex or getslice instead raise NotImplementedError def getindex_msb0(self, index: int, /) -> bool: return self._bits.__getitem__(index) def getslice_withstep_msb0(self, key: slice, /) -> ConstBitStore: return ConstBitStore(self._bits.__getitem__(key)) def getslice_withstep_lsb0(self, key: slice, /) -> ConstBitStore: key = offset_slice_indices_lsb0(key, len(self)) return ConstBitStore(self._bits.__getitem__(key)) def getslice_msb0(self, start: Optional[int], stop: Optional[int], /) -> ConstBitStore: return ConstBitStore(self._bits[start:stop]) def getslice_lsb0(self, start: Optional[int], stop: Optional[int], /) -> ConstBitStore: s = offset_slice_indices_lsb0(slice(start, stop, None), len(self)) return ConstBitStore(self._bits[s.start:s.stop]) def getindex_lsb0(self, index: int, /) -> bool: return self._bits.__getitem__(-index - 1) def any(self) -> bool: return self._bits.any() def all(self) -> bool: return self._bits.all() def __len__(self) -> int: return len(self._bits) class MutableBitStore: """A light wrapper around tibs.Mutibs that does the LSB0 stuff""" __slots__ = ('_bits',) def __init__(self, initializer: Union[Mutibs, Tibs, None] = None) -> None: if initializer is not None: self._bits = initializer else: self._bits = Mutibs() @classmethod def from_zeros(cls, i: int): x = super().__new__(cls) x._bits = Mutibs.from_zeros(i) return x @classmethod def from_mutibs(cls, mb: Mutibs): assert isinstance(mb, Mutibs) x = super().__new__(cls) x._bits = mb return x @classmethod def from_bytes(cls, b: Union[bytes, bytearray, memoryview], /) -> MutableBitStore: x = super().__new__(cls) x._bits = Mutibs.from_bytes(b) return x @classmethod def frombuffer(cls, buffer, /, length: Optional[int] = None) -> MutableBitStore: x = super().__new__(cls) # TODO: tibs needs a Bits.from_buffer method. x._bits = Mutibs.from_bytes(bytes(buffer)) if length is not None: if length < 0: raise CreationError("Can't create bitstring with a negative length.") if length > len(x._bits): raise CreationError( f"Can't create bitstring with a length of {length} from {len(x._bits)} bits of data.") return x.getslice(0, length) if length is not None else x @classmethod def from_bin(cls, s: str) -> MutableBitStore: x = super().__new__(cls) x._bits = Mutibs.from_bin(s) return x def set(self, value, pos) -> None: self._bits.set(value, pos) @staticmethod def using_rust_core() -> bool: return True def tobitarray(self): raise TypeError("tobitarray() is not available when using the Rust core option.") def to_bytes(self, pad_at_end: bool = True) -> bytes: excess_bits = len(self._bits) % 8 if excess_bits != 0: # Pad with zeros to make full bytes if pad_at_end: padded_bits = self._bits + Mutibs.from_zeros(8 - excess_bits) else: padded_bits = Mutibs.from_zeros(8 - excess_bits) + self._bits return padded_bits.to_bytes() return self._bits.to_bytes() def to_u(self) -> int: if len(self) > 128: return int.from_bytes(self.to_bytes(pad_at_end=False), byteorder="big", signed=False) try: return self._bits.to_u() except OverflowError as e: raise ValueError(e) def to_i(self) -> int: if len(self) > 128: return int.from_bytes(self.to_bytes(pad_at_end=False), byteorder="big", signed=True) try: return self._bits.to_i() except OverflowError as e: raise ValueError(e) def to_hex(self) -> str: return self._bits.to_hex() def to_bin(self) -> str: return self._bits.to_bin() def to_oct(self) -> str: return self._bits.to_oct() def __imul__(self, n: int, /) -> None: self._bits *= n def __ilshift__(self, n: int, /) -> None: self._bits <<= n def __irshift__(self, n: int, /) -> None: self._bits >>= n def __iadd__(self, other: MutableBitStore, /) -> MutableBitStore: self._bits += other._bits return self def __add__(self, other: MutableBitStore, /) -> MutableBitStore: bs = self._mutable_copy() bs += other return bs def __eq__(self, other: Any, /) -> bool: return self._bits == other._bits def __and__(self, other: MutableBitStore, /) -> MutableBitStore: return MutableBitStore.from_mutibs(self._bits & other._bits) def __or__(self, other: MutableBitStore, /) -> MutableBitStore: return MutableBitStore.from_mutibs(self._bits | other._bits) def __xor__(self, other: MutableBitStore, /) -> MutableBitStore: return MutableBitStore.from_mutibs(self._bits ^ other._bits) def __iand__(self, other: MutableBitStore, /) -> MutableBitStore: self._bits &= other._bits return self def __ior__(self, other: MutableBitStore, /) -> MutableBitStore: self._bits |= other._bits return self def __ixor__(self, other: MutableBitStore, /) -> MutableBitStore: self._bits ^= other._bits return self def __invert__(self) -> MutableBitStore: return MutableBitStore.from_mutibs(~self._bits) def find(self, bs: MutableBitStore, start: int, end: int, bytealigned: bool = False) -> int: assert start >= 0 return self._bits.find(bs._bits, start, end, byte_aligned=bytealigned) def rfind(self, bs: MutableBitStore, start: int, end: int, bytealigned: bool = False): assert start >= 0 return self._bits.rfind(bs._bits, start, end, byte_aligned=bytealigned) def findall_msb0(self, bs: MutableBitStore, start: int, end: int, bytealigned: bool = False) -> Iterator[int]: x = self._bits.to_tibs() for p in x.find_all(bs._bits, start=start, end=end, byte_aligned=bytealigned): yield p def rfindall_msb0(self, bs: MutableBitStore, start: int, end: int, bytealigned: bool = False) -> Iterator[int]: x = self._bits.to_tibs() for p in x.rfind_all(bs._bits, start=start, end=end, byte_aligned=bytealigned): yield p def count(self, value, /) -> int: return self._bits.count(value) def clear(self) -> None: self._bits.clear() def reverse(self) -> None: self._bits.reverse() def __iter__(self) -> Iterable[bool]: for i in range(len(self)): yield self.getindex(i) def extend_left(self, other: MutableBitStore, /) -> None: self._bits.extend_left(other._bits) def _mutable_copy(self) -> MutableBitStore: """Always creates a copy, even if instance is immutable.""" return MutableBitStore.from_mutibs(self._bits.__copy__()) def copy(self) -> MutableBitStore: return self._mutable_copy() def __getitem__(self, item: Union[int, slice], /) -> Union[int, MutableBitStore]: # Use getindex or getslice instead raise NotImplementedError def getindex_msb0(self, index: int, /) -> bool: return self._bits.__getitem__(index) def getslice_withstep_msb0(self, key: slice, /) -> MutableBitStore: return MutableBitStore(self._bits.__getitem__(key)) def getslice_withstep_lsb0(self, key: slice, /) -> MutableBitStore: key = offset_slice_indices_lsb0(key, len(self)) return MutableBitStore(self._bits.__getitem__(key)) def getslice_msb0(self, start: Optional[int], stop: Optional[int], /) -> MutableBitStore: return MutableBitStore(self._bits[start:stop]) def getslice_lsb0(self, start: Optional[int], stop: Optional[int], /) -> MutableBitStore: s = offset_slice_indices_lsb0(slice(start, stop, None), len(self)) return MutableBitStore(self._bits[s.start:s.stop]) def getindex_lsb0(self, index: int, /) -> bool: return self._bits.__getitem__(-index - 1) @overload def setitem_lsb0(self, key: int, value: int, /) -> None: ... @overload def setitem_lsb0(self, key: slice, value: MutableBitStore, /) -> None: ... def setitem_lsb0(self, key: Union[int, slice], value: Union[int, MutableBitStore], /) -> None: if isinstance(key, slice): new_slice = offset_slice_indices_lsb0(key, len(self)) self._bits.__setitem__(new_slice, value._bits) else: self._bits.__setitem__(-key - 1, bool(value)) def delitem_lsb0(self, key: Union[int, slice], /) -> None: if isinstance(key, slice): new_slice = offset_slice_indices_lsb0(key, len(self)) self._bits.__delitem__(new_slice) else: self._bits.__delitem__(-key - 1) def invert_msb0(self, index: Optional[int] = None, /) -> None: if index is not None: self._bits.invert(index) else: self._bits.invert() def invert_lsb0(self, index: Optional[int] = None, /) -> None: if index is not None: self._bits.invert(-index - 1) else: self._bits.invert() def any(self) -> bool: return self._bits.any() def all(self) -> bool: return self._bits.all() def __len__(self) -> int: return len(self._bits) def setitem_msb0(self, key, value, /): if isinstance(value, (MutableBitStore, ConstBitStore)): self._bits.__setitem__(key, value._bits) else: if isinstance(key, slice): key = range(*key.indices(len(self))) self._bits.set(value, key) def delitem_msb0(self, key, /): self._bits.__delitem__(key)