git.s-ol.nu subv / master bits.py
master

Tree @master (Download .tar.gz)

bits.py @masterraw · history · blame

class Bitfield(object):
    def __init__(self, val, size, extra=()):
        if val < 0:
            min = -(1 << (size - 1))
            max = -1 - min

            if val > max or val < min:
                raise ValueError(
                    "value {} out of i{} range [{};{}]".format(val, size, min, max)
                )

            if val < 0:
                val = (1 << size) + val

        self.val = val
        self.size = size
        self.extra = extra

    def __repr__(self):
        """format for debugging.
        >>> Bitfield(0, 8)
        Bitfield(0x0, 8)
        >>> Bitfield(16, 8)
        Bitfield(0x10, 8)
        >>> Bitfield(-16, 8)
        Bitfield(0xf0, 8)
        >>> Bitfield(3, 5)
        Bitfield(0x3, 5)
        >>> Bitfield(3, 4, extra=('hello',))
        Bitfield(0x3, 4, extra=('hello',))
        """
        extra = ""
        if self.extra:
            extra = ", extra={!r}".format(self.extra)
        return "Bitfield(0x{:x}, {}{})".format(self.val, self.size, extra)

    def __str__(self):
        """format as a word.
        >>> str(Bitfield(0, 8))
        '00/8'
        >>> str(Bitfield(16, 8))
        '10/8'
        >>> str(Bitfield(3, 8))
        '03/8'
        >>> str(Bitfield(3, 5))
        '03/5'
        >>> str(Bitfield(3, 4))
        '3/4'
        >>> str(Bitfield(3, 4, extra=('hello',)))
        '3/hello/4'
        """
        digits = 1 + ((self.size - 1) // 4)
        base = "{:x}".format(self.val)
        base = (digits - len(base)) * "0" + base
        return "/".join([base, *self.extra, str(self.size)])

    def __getitem__(self, range):
        """slice a bitfield given hi and lo bit indices.

        >>> Bitfield(0x7f, 8)[7:4]
        Bitfield(0x7, 4)
        >>> Bitfield(0b100, 3)[2:2]
        Bitfield(0x1, 1)
        >>> Bitfield(0x12345678, 32)[7:0]
        Bitfield(0x78, 8)
        >>> Bitfield(0x12345678, 32)[15:8]
        Bitfield(0x56, 8)
        >>> Bitfield(0x12345678, 32)[23:16]
        Bitfield(0x34, 8)

        >>> Bitfield(0xf, 4)[4:0]
        Traceback (most recent call last):
          ...
        ValueError: slice [4:0] out of range of f/4
        >>> Bitfield(0xf, 4)[2:3]
        Traceback (most recent call last):
          ...
        ValueError: cant slice reverse range
        >>> Bitfield(0xf, 4)[3:-1]
        Traceback (most recent call last):
          ...
        ValueError: slice [3:-1] out of range of f/4
        """
        if not isinstance(range, slice):
            range = slice(range, range)
        hi, lo = range.start, range.stop

        if hi < lo:
            raise ValueError("cant slice reverse range")
        elif lo < 0 or hi >= self.size:
            raise ValueError(
                "slice [{0.start}:{0.stop}] out of range of {1}".format(range, self)
            )

        size = hi - lo + 1
        val = (self.val >> lo) & ((1 << size) - 1)
        return Bitfield(val, size)

    def slice_allowempty(self, range):
        if not isinstance(range, slice):
            range = slice(range, range)
        hi, lo = range.start, range.stop

        if hi < lo:
            return empty

        return self[range]

    def __and__(self, other):
        """concatenate multiple bitfields.

        >>> Bitfield(0b00, 2) & Bitfield(0b10, 2)
        Bitfield(0x2, 4)
        >>> Bitfield(0b110, 3) & Bitfield(0b0110, 4) & Bitfield(0b1, 1)
        Bitfield(0xcd, 8)
        """
        if isinstance(other, Bitfield):
            return Bitfield(self.val << other.size | other.val, self.size + other.size)
        raise NotImplementedError()


global_slice = slice

empty = Bitfield(0, 0)


def u(num, bits):
    """parse an unsigned integer into a bitfield.

    >>> u(0x08, 8)
    Bitfield(0x8, 8)
    >>> u(0xff, 8)
    Bitfield(0xff, 8)

    >>> u(0xf0, 7)
    Traceback (most recent call last):
      ...
    ValueError: value 240 (u8) too large for u7 field
    >>> u(-1, 8)
    Traceback (most recent call last):
      ...
    ValueError: negative value not allowed: -1
    """
    if num < 0:
        raise ValueError("negative value not allowed: {}".format(num))

    if num.bit_length() > bits:
        raise ValueError(
            "value {} (u{}) too large for u{} field".format(num, num.bit_length(), bits)
        )

    return Bitfield(num, bits)


def i(num, bits):
    """parse a signed integer into a bitfield.

    >>> i(8, 8)
    Bitfield(0x8, 8)
    >>> i(-4, 8)
    Bitfield(0xfc, 8)
    >>> i(0x7f, 8)
    Bitfield(0x7f, 8)
    >>> i(-128, 8)
    Bitfield(0x80, 8)

    >>> i(256, 8)
    Traceback (most recent call last):
      ...
    ValueError: value 256 (u9) too large for u8 field
    >>> i(-129, 8)
    Traceback (most recent call last):
      ...
    ValueError: value -129 (i9) too small for i8 field (min -128)
    """
    min = -(1 << (bits - 1))

    if num < min:
        raise ValueError(
            "value {} (i{}) too small for i{} field (min {})".format(
                num, num.bit_length() + 1, bits, min
            )
        )

    if num < 0:
        num = (1 << bits) + num

    return u(num, bits)


def from_part(part):
    """parse a size-tagged subv part into a bit.

    >>> from_part((0x12, 2))
    Bitfield(0x12, 2)
    >>> from_part((0x12, 2, 'extra'))
    Bitfield(0x12, 2)
    """
    val = int(part[0])
    size = part[1]
    return Bitfield(val, int(size))