Add Code
This commit is contained in:
337
code/.venv/lib/python3.12/site-packages/bitstring/__init__.py
Normal file
337
code/.venv/lib/python3.12/site-packages/bitstring/__init__.py
Normal file
@@ -0,0 +1,337 @@
|
||||
#!/usr/bin/env python
|
||||
r"""
|
||||
This package defines classes that simplify bit-wise creation, manipulation and
|
||||
interpretation of data.
|
||||
|
||||
Classes:
|
||||
|
||||
Bits -- An immutable container for binary data.
|
||||
BitArray -- A mutable container for binary data.
|
||||
ConstBitStream -- An immutable container with streaming methods.
|
||||
BitStream -- A mutable container with streaming methods.
|
||||
Array -- An efficient list-like container where each item has a fixed-length binary format.
|
||||
Dtype -- Encapsulate the data types used in the other classes.
|
||||
|
||||
Functions:
|
||||
|
||||
pack -- Create a BitStream from a format string.
|
||||
|
||||
Data:
|
||||
|
||||
options -- Module-wide options.
|
||||
|
||||
Exceptions:
|
||||
|
||||
Error -- Module exception base class.
|
||||
CreationError -- Error during creation.
|
||||
InterpretError -- Inappropriate interpretation of binary data.
|
||||
ByteAlignError -- Whole byte position or length needed.
|
||||
ReadError -- Reading or peeking past the end of a bitstring.
|
||||
|
||||
https://github.com/scott-griffiths/bitstring
|
||||
"""
|
||||
|
||||
__licence__ = """
|
||||
The MIT License
|
||||
|
||||
Copyright (c) 2006 Scott Griffiths (dr.scottgriffiths@gmail.com)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
"""
|
||||
|
||||
__version__ = "4.2.3"
|
||||
|
||||
__author__ = "Scott Griffiths"
|
||||
|
||||
import sys
|
||||
|
||||
from .bits import Bits
|
||||
from .bitstring_options import Options
|
||||
from .bitarray_ import BitArray
|
||||
from .bitstream import ConstBitStream, BitStream
|
||||
from .methods import pack
|
||||
from .array_ import Array
|
||||
from .exceptions import Error, ReadError, InterpretError, ByteAlignError, CreationError
|
||||
from .dtypes import DtypeDefinition, dtype_register, Dtype
|
||||
import types
|
||||
from typing import List, Tuple, Literal
|
||||
from .mxfp import decompress_luts as mxfp_decompress_luts
|
||||
from .fp8 import decompress_luts as binary8_decompress_luts
|
||||
|
||||
# Decompress the LUTs for the exotic floating point formats
|
||||
mxfp_decompress_luts()
|
||||
binary8_decompress_luts()
|
||||
|
||||
# The Options class returns a singleton.
|
||||
options = Options()
|
||||
|
||||
# These get defined properly by the module magic below. This just stops mypy complaining about them.
|
||||
bytealigned = lsb0 = None
|
||||
|
||||
|
||||
# An opaque way of adding module level properties. Taken from https://peps.python.org/pep-0549/
|
||||
# This is now deprecated. Use the options object directly instead.
|
||||
class _MyModuleType(types.ModuleType):
|
||||
@property
|
||||
def bytealigned(self) -> bool:
|
||||
"""Determines whether a number of methods default to working only on byte boundaries."""
|
||||
return options.bytealigned
|
||||
|
||||
@bytealigned.setter
|
||||
def bytealigned(self, value: bool) -> None:
|
||||
"""Determines whether a number of methods default to working only on byte boundaries."""
|
||||
options.bytealigned = value
|
||||
|
||||
@property
|
||||
def lsb0(self) -> bool:
|
||||
"""If True, the least significant bit (the final bit) is indexed as bit zero."""
|
||||
return options.lsb0
|
||||
|
||||
@lsb0.setter
|
||||
def lsb0(self, value: bool) -> None:
|
||||
"""If True, the least significant bit (the final bit) is indexed as bit zero."""
|
||||
options.lsb0 = value
|
||||
|
||||
|
||||
sys.modules[__name__].__class__ = _MyModuleType
|
||||
|
||||
|
||||
# These methods convert a bit length to the number of characters needed to print it for different interpretations.
|
||||
def hex_bits2chars(bitlength: int):
|
||||
# One character for every 4 bits
|
||||
return bitlength // 4
|
||||
|
||||
|
||||
def oct_bits2chars(bitlength: int):
|
||||
# One character for every 3 bits
|
||||
return bitlength // 3
|
||||
|
||||
|
||||
def bin_bits2chars(bitlength: int):
|
||||
# One character for each bit
|
||||
return bitlength
|
||||
|
||||
|
||||
def bytes_bits2chars(bitlength: int):
|
||||
# One character for every 8 bits
|
||||
return bitlength // 8
|
||||
|
||||
|
||||
def uint_bits2chars(bitlength: int):
|
||||
# How many characters is largest possible int of this length?
|
||||
return len(str((1 << bitlength) - 1))
|
||||
|
||||
|
||||
def int_bits2chars(bitlength: int):
|
||||
# How many characters is largest negative int of this length? (To include minus sign).
|
||||
return len(str((-1 << (bitlength - 1))))
|
||||
|
||||
|
||||
def float_bits2chars(bitlength: Literal[16, 32, 64]):
|
||||
# These bit lengths were found by looking at lots of possible values
|
||||
if bitlength in [16, 32]:
|
||||
return 23 # Empirical value
|
||||
else:
|
||||
return 24 # Empirical value
|
||||
|
||||
|
||||
def p3binary_bits2chars(_: Literal[8]):
|
||||
return 19 # Empirical value
|
||||
|
||||
|
||||
def p4binary_bits2chars(_: Literal[8]):
|
||||
# Found by looking at all the possible values
|
||||
return 13 # Empirical value
|
||||
|
||||
|
||||
def e4m3mxfp_bits2chars(_: Literal[8]):
|
||||
return 13
|
||||
|
||||
|
||||
def e5m2mxfp_bits2chars(_: Literal[8]):
|
||||
return 19
|
||||
|
||||
|
||||
def e3m2mxfp_bits2chars(_: Literal[6]):
|
||||
# Not sure what the best value is here. It's 7 without considering the scale that could be applied.
|
||||
return 7
|
||||
|
||||
|
||||
def e2m3mxfp_bits2chars(_: Literal[6]):
|
||||
# Not sure what the best value is here.
|
||||
return 7
|
||||
|
||||
|
||||
def e2m1mxfp_bits2chars(_: Literal[4]):
|
||||
# Not sure what the best value is here.
|
||||
return 7
|
||||
|
||||
|
||||
def e8m0mxfp_bits2chars(_: Literal[8]):
|
||||
# Has same range as float32
|
||||
return 23
|
||||
|
||||
|
||||
def mxint_bits2chars(_: Literal[8]):
|
||||
# Not sure what the best value is here.
|
||||
return 10
|
||||
|
||||
|
||||
def bfloat_bits2chars(_: Literal[16]):
|
||||
# Found by looking at all the possible values
|
||||
return 23 # Empirical value
|
||||
|
||||
|
||||
def bits_bits2chars(bitlength: int):
|
||||
# For bits type we can see how long it needs to be printed by trying any value
|
||||
temp = Bits(bitlength)
|
||||
return len(str(temp))
|
||||
|
||||
|
||||
def bool_bits2chars(_: Literal[1]):
|
||||
# Bools are printed as 1 or 0, not True or False, so are one character each
|
||||
return 1
|
||||
|
||||
|
||||
dtype_definitions = [
|
||||
# Integer types
|
||||
DtypeDefinition('uint', Bits._setuint, Bits._getuint, int, False, uint_bits2chars,
|
||||
description="a two's complement unsigned int"),
|
||||
DtypeDefinition('uintle', Bits._setuintle, Bits._getuintle, int, False, uint_bits2chars,
|
||||
allowed_lengths=(8, 16, 24, ...), description="a two's complement little-endian unsigned int"),
|
||||
DtypeDefinition('uintbe', Bits._setuintbe, Bits._getuintbe, int, False, uint_bits2chars,
|
||||
allowed_lengths=(8, 16, 24, ...), description="a two's complement big-endian unsigned int"),
|
||||
DtypeDefinition('int', Bits._setint, Bits._getint, int, True, int_bits2chars,
|
||||
description="a two's complement signed int"),
|
||||
DtypeDefinition('intle', Bits._setintle, Bits._getintle, int, True, int_bits2chars,
|
||||
allowed_lengths=(8, 16, 24, ...), description="a two's complement little-endian signed int"),
|
||||
DtypeDefinition('intbe', Bits._setintbe, Bits._getintbe, int, True, int_bits2chars,
|
||||
allowed_lengths=(8, 16, 24, ...), description="a two's complement big-endian signed int"),
|
||||
# String types
|
||||
DtypeDefinition('hex', Bits._sethex, Bits._gethex, str, False, hex_bits2chars,
|
||||
allowed_lengths=(0, 4, 8, ...), description="a hexadecimal string"),
|
||||
DtypeDefinition('bin', Bits._setbin_safe, Bits._getbin, str, False, bin_bits2chars,
|
||||
description="a binary string"),
|
||||
DtypeDefinition('oct', Bits._setoct, Bits._getoct, str, False, oct_bits2chars,
|
||||
allowed_lengths=(0, 3, 6, ...), description="an octal string"),
|
||||
# Float types
|
||||
DtypeDefinition('float', Bits._setfloatbe, Bits._getfloatbe, float, True, float_bits2chars,
|
||||
allowed_lengths=(16, 32, 64), description="a big-endian floating point number"),
|
||||
DtypeDefinition('floatle', Bits._setfloatle, Bits._getfloatle, float, True, float_bits2chars,
|
||||
allowed_lengths=(16, 32, 64), description="a little-endian floating point number"),
|
||||
DtypeDefinition('bfloat', Bits._setbfloatbe, Bits._getbfloatbe, float, True, bfloat_bits2chars,
|
||||
allowed_lengths=(16,), description="a 16 bit big-endian bfloat floating point number"),
|
||||
DtypeDefinition('bfloatle', Bits._setbfloatle, Bits._getbfloatle, float, True, bfloat_bits2chars,
|
||||
allowed_lengths=(16,), description="a 16 bit little-endian bfloat floating point number"),
|
||||
# Other known length types
|
||||
DtypeDefinition('bits', Bits._setbits, Bits._getbits, Bits, False, bits_bits2chars,
|
||||
description="a bitstring object"),
|
||||
DtypeDefinition('bool', Bits._setbool, Bits._getbool, bool, False, bool_bits2chars,
|
||||
allowed_lengths=(1,), description="a bool (True or False)"),
|
||||
DtypeDefinition('bytes', Bits._setbytes, Bits._getbytes, bytes, False, bytes_bits2chars,
|
||||
multiplier=8, description="a bytes object"),
|
||||
# Unknown length types
|
||||
DtypeDefinition('se', Bits._setse, Bits._getse, int, True, None,
|
||||
variable_length=True, description="a signed exponential-Golomb code"),
|
||||
DtypeDefinition('ue', Bits._setue, Bits._getue, int, False, None,
|
||||
variable_length=True, description="an unsigned exponential-Golomb code"),
|
||||
DtypeDefinition('sie', Bits._setsie, Bits._getsie, int, True, None,
|
||||
variable_length=True, description="a signed interleaved exponential-Golomb code"),
|
||||
DtypeDefinition('uie', Bits._setuie, Bits._getuie, int, False, None,
|
||||
variable_length=True, description="an unsigned interleaved exponential-Golomb code"),
|
||||
# Special case pad type
|
||||
DtypeDefinition('pad', Bits._setpad, Bits._getpad, None, False, None,
|
||||
description="a skipped section of padding"),
|
||||
|
||||
# MXFP and IEEE 8-bit float types
|
||||
DtypeDefinition('p3binary', Bits._setp3binary, Bits._getp3binary, float, True, p3binary_bits2chars,
|
||||
allowed_lengths=(8,), description="an 8 bit float with binary8p3 format"),
|
||||
DtypeDefinition('p4binary', Bits._setp4binary, Bits._getp4binary, float, True, p4binary_bits2chars,
|
||||
allowed_lengths=(8,), description="an 8 bit float with binary8p4 format"),
|
||||
DtypeDefinition('e4m3mxfp', Bits._sete4m3mxfp, Bits._gete4m3mxfp, float, True, e4m3mxfp_bits2chars,
|
||||
allowed_lengths=(8,), description="an 8 bit float with MXFP E4M3 format"),
|
||||
DtypeDefinition('e5m2mxfp', Bits._sete5m2mxfp, Bits._gete5m2mxfp, float, True, e5m2mxfp_bits2chars,
|
||||
allowed_lengths=(8,), description="an 8 bit float with MXFP E5M2 format"),
|
||||
DtypeDefinition('e3m2mxfp', Bits._sete3m2mxfp, Bits._gete3m2mxfp, float, True, e3m2mxfp_bits2chars,
|
||||
allowed_lengths=(6,), description="a 6 bit float with MXFP E3M2 format"),
|
||||
DtypeDefinition('e2m3mxfp', Bits._sete2m3mxfp, Bits._gete2m3mxfp, float, True, e2m3mxfp_bits2chars,
|
||||
allowed_lengths=(6,), description="a 6 bit float with MXFP E2M3 format"),
|
||||
DtypeDefinition('e2m1mxfp', Bits._sete2m1mxfp, Bits._gete2m1mxfp, float, True, e2m1mxfp_bits2chars,
|
||||
allowed_lengths=(4,), description="a 4 bit float with MXFP E2M1 format"),
|
||||
DtypeDefinition('e8m0mxfp', Bits._sete8m0mxfp, Bits._gete8m0mxfp, float, False, e8m0mxfp_bits2chars,
|
||||
allowed_lengths=(8,), description="an 8 bit float with MXFP E8M0 format"),
|
||||
DtypeDefinition('mxint', Bits._setmxint, Bits._getmxint, float, True, mxint_bits2chars,
|
||||
allowed_lengths=(8,), description="an 8 bit float with MXFP INT8 format"),
|
||||
]
|
||||
|
||||
|
||||
aliases: List[Tuple[str, str]] = [
|
||||
# Floats default to big endian
|
||||
('float', 'floatbe'),
|
||||
('bfloat', 'bfloatbe'),
|
||||
|
||||
# Some single letter aliases for popular types
|
||||
('int', 'i'),
|
||||
('uint', 'u'),
|
||||
('hex', 'h'),
|
||||
('oct', 'o'),
|
||||
('bin', 'b'),
|
||||
('float', 'f'),
|
||||
]
|
||||
|
||||
# Create native-endian aliases depending on the byteorder of the system
|
||||
byteorder: str = sys.byteorder
|
||||
if byteorder == 'little':
|
||||
aliases.extend([
|
||||
('uintle', 'uintne'),
|
||||
('intle', 'intne'),
|
||||
('floatle', 'floatne'),
|
||||
('bfloatle', 'bfloatne'),
|
||||
])
|
||||
else:
|
||||
aliases.extend([
|
||||
('uintbe', 'uintne'),
|
||||
('intbe', 'intne'),
|
||||
('floatbe', 'floatne'),
|
||||
('bfloatbe', 'bfloatne'),
|
||||
])
|
||||
|
||||
|
||||
for dt in dtype_definitions:
|
||||
dtype_register.add_dtype(dt)
|
||||
for alias in aliases:
|
||||
dtype_register.add_dtype_alias(alias[0], alias[1])
|
||||
|
||||
property_docstrings = [f'{name} -- Interpret as {dtype_register[name].description}.' for name in dtype_register.names]
|
||||
property_docstring = '\n '.join(property_docstrings)
|
||||
|
||||
# We can't be sure the docstrings are present, as it might be compiled without docstrings.
|
||||
if Bits.__doc__ is not None:
|
||||
Bits.__doc__ = Bits.__doc__.replace('[GENERATED_PROPERTY_DESCRIPTIONS]', property_docstring)
|
||||
if BitArray.__doc__ is not None:
|
||||
BitArray.__doc__ = BitArray.__doc__.replace('[GENERATED_PROPERTY_DESCRIPTIONS]', property_docstring)
|
||||
if ConstBitStream.__doc__ is not None:
|
||||
ConstBitStream.__doc__ = ConstBitStream.__doc__.replace('[GENERATED_PROPERTY_DESCRIPTIONS]', property_docstring)
|
||||
if BitStream.__doc__ is not None:
|
||||
BitStream.__doc__ = BitStream.__doc__.replace('[GENERATED_PROPERTY_DESCRIPTIONS]', property_docstring)
|
||||
|
||||
|
||||
__all__ = ['ConstBitStream', 'BitStream', 'BitArray', 'Array',
|
||||
'Bits', 'pack', 'Error', 'ReadError', 'InterpretError',
|
||||
'ByteAlignError', 'CreationError', 'bytealigned', 'lsb0', 'Dtype', 'options']
|
||||
@@ -0,0 +1,50 @@
|
||||
import sys
|
||||
from bitstring.bits import Bits
|
||||
from bitstring.dtypes import Register
|
||||
|
||||
dtype_register = Register()
|
||||
|
||||
|
||||
def main() -> None:
|
||||
# check if final parameter is an interpretation string
|
||||
fp = sys.argv[-1]
|
||||
if fp in ['-h', '--help'] or len(sys.argv) == 1:
|
||||
print("""Create and interpret a bitstring from command-line parameters.
|
||||
|
||||
Command-line parameters are concatenated and a bitstring created
|
||||
from them. If the final parameter is either an interpretation string
|
||||
or ends with a '.' followed by an interpretation string then that
|
||||
interpretation of the bitstring will be used when printing it.
|
||||
|
||||
Typical usage might be invoking the Python module from a console
|
||||
as a one-off calculation:
|
||||
|
||||
$ python -m bitstring int:16=-400
|
||||
0xfe70
|
||||
$ python -m bitstring float:32=0.2 bin
|
||||
00111110010011001100110011001101
|
||||
$ python -m bitstring 0xff 3*0b01,0b11 uint
|
||||
65367
|
||||
$ python -m bitstring hex=01, uint:12=352.hex
|
||||
01160
|
||||
""")
|
||||
return
|
||||
if fp in dtype_register.names:
|
||||
# concatenate all other parameters and interpret using the final one
|
||||
b1 = Bits(','.join(sys.argv[1: -1]))
|
||||
print(b1._readtoken(fp, 0, b1.__len__())[0])
|
||||
else:
|
||||
# does final parameter end with a dot then an interpretation string?
|
||||
interp = fp[fp.rfind('.') + 1:]
|
||||
if interp in dtype_register.names:
|
||||
sys.argv[-1] = fp[:fp.rfind('.')]
|
||||
b1 = Bits(','.join(sys.argv[1:]))
|
||||
print(b1._readtoken(interp, 0, b1.__len__())[0])
|
||||
else:
|
||||
# No interpretation - just use default print
|
||||
b1 = Bits(','.join(sys.argv[1:]))
|
||||
print(b1)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
782
code/.venv/lib/python3.12/site-packages/bitstring/array_.py
Normal file
782
code/.venv/lib/python3.12/site-packages/bitstring/array_.py
Normal file
@@ -0,0 +1,782 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import math
|
||||
import numbers
|
||||
from collections.abc import Sized
|
||||
from bitstring.exceptions import CreationError
|
||||
from typing import Union, List, Iterable, Any, Optional, BinaryIO, overload, TextIO
|
||||
from bitstring.bits import Bits, BitsType
|
||||
from bitstring.bitarray_ import BitArray
|
||||
from bitstring.dtypes import Dtype, dtype_register
|
||||
from bitstring import utils
|
||||
from bitstring.bitstring_options import Options, Colour
|
||||
import copy
|
||||
import array
|
||||
import operator
|
||||
import io
|
||||
import sys
|
||||
|
||||
# The possible types stored in each element of the Array
|
||||
ElementType = Union[float, str, int, bytes, bool, Bits]
|
||||
|
||||
options = Options()
|
||||
|
||||
|
||||
class Array:
|
||||
"""Return an Array whose elements are initialised according to the fmt string.
|
||||
The dtype string can be typecode as used in the struct module or any fixed-length bitstring
|
||||
format.
|
||||
|
||||
a = Array('>H', [1, 15, 105])
|
||||
b = Array('int5', [-9, 0, 4])
|
||||
|
||||
The Array data is stored compactly as a BitArray object and the Array behaves very like
|
||||
a list of items of the given format. Both the Array data and fmt properties can be freely
|
||||
modified after creation. If the data length is not a multiple of the fmt length then the
|
||||
Array will have 'trailing_bits' which will prevent some methods from appending to the
|
||||
Array.
|
||||
|
||||
Methods:
|
||||
|
||||
append() -- Append a single item to the end of the Array.
|
||||
byteswap() -- Change byte endianness of all items.
|
||||
count() -- Count the number of occurences of a value.
|
||||
extend() -- Append new items to the end of the Array from an iterable.
|
||||
fromfile() -- Append items read from a file object.
|
||||
insert() -- Insert an item at a given position.
|
||||
pop() -- Remove and return an item.
|
||||
pp() -- Pretty print the Array.
|
||||
reverse() -- Reverse the order of all items.
|
||||
tobytes() -- Return Array data as bytes object, padding with zero bits at the end if needed.
|
||||
tofile() -- Write Array data to a file, padding with zero bits at the end if needed.
|
||||
tolist() -- Return Array items as a list.
|
||||
|
||||
Special methods:
|
||||
|
||||
Also available are the operators [], ==, !=, +, *, <<, >>, &, |, ^,
|
||||
plus the mutating operators [], +=, *=, <<=, >>=, &=, |=, ^=.
|
||||
|
||||
Properties:
|
||||
|
||||
data -- The BitArray binary data of the Array. Can be freely modified.
|
||||
dtype -- The format string or typecode. Can be freely modified.
|
||||
itemsize -- The length *in bits* of a single item. Read only.
|
||||
trailing_bits -- If the data length is not a multiple of the fmt length, this BitArray
|
||||
gives the leftovers at the end of the data.
|
||||
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, dtype: Union[str, Dtype], initializer: Optional[Union[int, Array, array.array, Iterable, Bits, bytes, bytearray, memoryview, BinaryIO]] = None,
|
||||
trailing_bits: Optional[BitsType] = None) -> None:
|
||||
self.data = BitArray()
|
||||
if isinstance(dtype, Dtype) and dtype.scale == 'auto':
|
||||
if isinstance(initializer, (int, Bits, bytes, bytearray, memoryview, BinaryIO)):
|
||||
raise TypeError("An Array with an 'auto' scale factor can only be created from an iterable of values.")
|
||||
auto_scale = self._calculate_auto_scale(initializer, dtype.name, dtype.length)
|
||||
dtype = Dtype(dtype.name, dtype.length, scale=auto_scale)
|
||||
try:
|
||||
self._set_dtype(dtype)
|
||||
except ValueError as e:
|
||||
raise CreationError(e)
|
||||
|
||||
if isinstance(initializer, numbers.Integral):
|
||||
self.data = BitArray(initializer * self._dtype.bitlength)
|
||||
elif isinstance(initializer, (Bits, bytes, bytearray, memoryview)):
|
||||
self.data += initializer
|
||||
elif isinstance(initializer, io.BufferedReader):
|
||||
self.fromfile(initializer)
|
||||
elif initializer is not None:
|
||||
self.extend(initializer)
|
||||
|
||||
if trailing_bits is not None:
|
||||
self.data += BitArray._create_from_bitstype(trailing_bits)
|
||||
|
||||
_largest_values = None
|
||||
|
||||
@staticmethod
|
||||
def _calculate_auto_scale(initializer, name: str, length: Optional[int]) -> float:
|
||||
# Now need to find the largest power of 2 representable with this format.
|
||||
if Array._largest_values is None:
|
||||
Array._largest_values = {
|
||||
'mxint8': Bits('0b01111111').mxint8, # 1.0 + 63.0/64.0,
|
||||
'e2m1mxfp4': Bits('0b0111').e2m1mxfp4, # 6.0
|
||||
'e2m3mxfp6': Bits('0b011111').e2m3mxfp6, # 7.5
|
||||
'e3m2mxfp6': Bits('0b011111').e3m2mxfp6, # 28.0
|
||||
'e4m3mxfp8': Bits('0b01111110').e4m3mxfp8, # 448.0
|
||||
'e5m2mxfp8': Bits('0b01111011').e5m2mxfp8, # 57344.0
|
||||
'p4binary8': Bits('0b01111110').p4binary8, # 224.0
|
||||
'p3binary8': Bits('0b01111110').p3binary8, # 49152.0
|
||||
'float16': Bits('0x7bff').float16, # 65504.0
|
||||
# The bfloat range is so large the scaling algorithm doesn't work well, so I'm disallowing it.
|
||||
# 'bfloat16': Bits('0x7f7f').bfloat16, # 3.38953139e38,
|
||||
}
|
||||
if f'{name}{length}' in Array._largest_values.keys():
|
||||
float_values = Array('float64', initializer).tolist()
|
||||
if not float_values:
|
||||
raise ValueError("Can't calculate an 'auto' scale with an empty Array initializer.")
|
||||
max_float_value = max(abs(x) for x in float_values)
|
||||
if max_float_value == 0:
|
||||
# This special case isn't covered in the standard. I'm choosing to return no scale.
|
||||
return 1.0
|
||||
# We need to find the largest power of 2 that is less than the max value
|
||||
log2 = math.floor(math.log2(max_float_value))
|
||||
lp2 = math.floor(math.log2(Array._largest_values[f'{name}{length}']))
|
||||
lg_scale = log2 - lp2
|
||||
# Saturate at values representable in E8M0 format.
|
||||
if lg_scale > 127:
|
||||
lg_scale = 127
|
||||
elif lg_scale < -127:
|
||||
lg_scale = -127
|
||||
return 2 ** lg_scale
|
||||
else:
|
||||
raise ValueError(f"Can't calculate auto scale for format '{name}{length}'. "
|
||||
f"This feature is only available for these formats: {list(Array._largest_values.keys())}.")
|
||||
|
||||
@property
|
||||
def itemsize(self) -> int:
|
||||
return self._dtype.length
|
||||
|
||||
@property
|
||||
def trailing_bits(self) -> BitArray:
|
||||
trailing_bit_length = len(self.data) % self._dtype.bitlength
|
||||
return BitArray() if trailing_bit_length == 0 else self.data[-trailing_bit_length:]
|
||||
|
||||
@property
|
||||
def dtype(self) -> Dtype:
|
||||
return self._dtype
|
||||
|
||||
@dtype.setter
|
||||
def dtype(self, new_dtype: Union[str, Dtype]) -> None:
|
||||
self._set_dtype(new_dtype)
|
||||
|
||||
def _set_dtype(self, new_dtype: Union[str, Dtype]) -> None:
|
||||
if isinstance(new_dtype, Dtype):
|
||||
self._dtype = new_dtype
|
||||
else:
|
||||
try:
|
||||
dtype = Dtype(new_dtype)
|
||||
except ValueError:
|
||||
name_length = utils.parse_single_struct_token(new_dtype)
|
||||
if name_length is not None:
|
||||
dtype = Dtype(name_length[0], name_length[1])
|
||||
else:
|
||||
raise ValueError(f"Inappropriate Dtype for Array: '{new_dtype}'.")
|
||||
if dtype.length is None:
|
||||
raise ValueError(f"A fixed length format is needed for an Array, received '{new_dtype}'.")
|
||||
self._dtype = dtype
|
||||
if self._dtype.scale == 'auto':
|
||||
raise ValueError("A Dtype with an 'auto' scale factor can only be used when creating a new Array.")
|
||||
|
||||
def _create_element(self, value: ElementType) -> Bits:
|
||||
"""Create Bits from value according to the token_name and token_length"""
|
||||
b = self._dtype.build(value)
|
||||
if len(b) != self._dtype.length:
|
||||
raise ValueError(f"The value {value!r} has the wrong length for the format '{self._dtype}'.")
|
||||
return b
|
||||
|
||||
def __len__(self) -> int:
|
||||
return len(self.data) // self._dtype.length
|
||||
|
||||
@overload
|
||||
def __getitem__(self, key: slice) -> Array:
|
||||
...
|
||||
|
||||
@overload
|
||||
def __getitem__(self, key: int) -> ElementType:
|
||||
...
|
||||
|
||||
def __getitem__(self, key: Union[slice, int]) -> Union[Array, ElementType]:
|
||||
if isinstance(key, slice):
|
||||
start, stop, step = key.indices(len(self))
|
||||
if step != 1:
|
||||
d = BitArray()
|
||||
for s in range(start * self._dtype.length, stop * self._dtype.length, step * self._dtype.length):
|
||||
d.append(self.data[s: s + self._dtype.length])
|
||||
a = self.__class__(self._dtype)
|
||||
a.data = d
|
||||
return a
|
||||
else:
|
||||
a = self.__class__(self._dtype)
|
||||
a.data = self.data[start * self._dtype.length: stop * self._dtype.length]
|
||||
return a
|
||||
else:
|
||||
if key < 0:
|
||||
key += len(self)
|
||||
if key < 0 or key >= len(self):
|
||||
raise IndexError(f"Index {key} out of range for Array of length {len(self)}.")
|
||||
return self._dtype.read_fn(self.data, start=self._dtype.length * key)
|
||||
|
||||
@overload
|
||||
def __setitem__(self, key: slice, value: Iterable[ElementType]) -> None:
|
||||
...
|
||||
|
||||
@overload
|
||||
def __setitem__(self, key: int, value: ElementType) -> None:
|
||||
...
|
||||
|
||||
def __setitem__(self, key: Union[slice, int], value: Union[Iterable[ElementType], ElementType]) -> None:
|
||||
if isinstance(key, slice):
|
||||
start, stop, step = key.indices(len(self))
|
||||
if not isinstance(value, Iterable):
|
||||
raise TypeError("Can only assign an iterable to a slice.")
|
||||
if step == 1:
|
||||
new_data = BitArray()
|
||||
for x in value:
|
||||
new_data += self._create_element(x)
|
||||
self.data[start * self._dtype.length: stop * self._dtype.length] = new_data
|
||||
return
|
||||
items_in_slice = len(range(start, stop, step))
|
||||
if not isinstance(value, Sized):
|
||||
value = list(value)
|
||||
if len(value) == items_in_slice:
|
||||
for s, v in zip(range(start, stop, step), value):
|
||||
self.data.overwrite(self._create_element(v), s * self._dtype.length)
|
||||
else:
|
||||
raise ValueError(f"Can't assign {len(value)} values to an extended slice of length {items_in_slice}.")
|
||||
else:
|
||||
if key < 0:
|
||||
key += len(self)
|
||||
if key < 0 or key >= len(self):
|
||||
raise IndexError(f"Index {key} out of range for Array of length {len(self)}.")
|
||||
start = self._dtype.length * key
|
||||
self.data.overwrite(self._create_element(value), start)
|
||||
return
|
||||
|
||||
def __delitem__(self, key: Union[slice, int]) -> None:
|
||||
if isinstance(key, slice):
|
||||
start, stop, step = key.indices(len(self))
|
||||
if step == 1:
|
||||
self.data.__delitem__(slice(start * self._dtype.length, stop * self._dtype.length))
|
||||
return
|
||||
# We need to delete from the end or the earlier positions will change
|
||||
r = reversed(range(start, stop, step)) if step > 0 else range(start, stop, step)
|
||||
for s in r:
|
||||
self.data.__delitem__(slice(s * self._dtype.length, (s + 1) * self._dtype.length))
|
||||
else:
|
||||
if key < 0:
|
||||
key += len(self)
|
||||
if key < 0 or key >= len(self):
|
||||
raise IndexError
|
||||
start = self._dtype.length * key
|
||||
del self.data[start: start + self._dtype.length]
|
||||
|
||||
def __repr__(self) -> str:
|
||||
list_str = f"{self.tolist()}"
|
||||
trailing_bit_length = len(self.data) % self._dtype.length
|
||||
final_str = "" if trailing_bit_length == 0 else ", trailing_bits=" + repr(
|
||||
self.data[-trailing_bit_length:])
|
||||
return f"Array('{self._dtype}', {list_str}{final_str})"
|
||||
|
||||
def astype(self, dtype: Union[str, Dtype]) -> Array:
|
||||
"""Return Array with elements of new dtype, initialised from current Array."""
|
||||
new_array = self.__class__(dtype, self.tolist())
|
||||
return new_array
|
||||
|
||||
def tolist(self) -> List[ElementType]:
|
||||
return [self._dtype.read_fn(self.data, start=start)
|
||||
for start in range(0, len(self.data) - self._dtype.length + 1, self._dtype.length)]
|
||||
|
||||
def append(self, x: ElementType) -> None:
|
||||
if len(self.data) % self._dtype.length != 0:
|
||||
raise ValueError("Cannot append to Array as its length is not a multiple of the format length.")
|
||||
self.data += self._create_element(x)
|
||||
|
||||
def extend(self, iterable: Union[Array, array.array, Iterable[Any]]) -> None:
|
||||
if len(self.data) % self._dtype.length != 0:
|
||||
raise ValueError(f"Cannot extend Array as its data length ({len(self.data)} bits) is not a multiple of the format length ({self._dtype.length} bits).")
|
||||
if isinstance(iterable, Array):
|
||||
if self._dtype.name != iterable._dtype.name or self._dtype.length != iterable._dtype.length:
|
||||
raise TypeError(
|
||||
f"Cannot extend an Array with format '{self._dtype}' from an Array of format '{iterable._dtype}'.")
|
||||
# No need to iterate over the elements, we can just append the data
|
||||
self.data.append(iterable.data)
|
||||
elif isinstance(iterable, array.array):
|
||||
# array.array types are always native-endian, hence the '='
|
||||
name_value = utils.parse_single_struct_token('=' + iterable.typecode)
|
||||
if name_value is None:
|
||||
raise ValueError(f"Cannot extend from array with typecode {iterable.typecode}.")
|
||||
other_dtype = dtype_register.get_dtype(*name_value, scale=None)
|
||||
if self._dtype.name != other_dtype.name or self._dtype.length != other_dtype.length:
|
||||
raise ValueError(
|
||||
f"Cannot extend an Array with format '{self._dtype}' from an array with typecode '{iterable.typecode}'.")
|
||||
self.data += iterable.tobytes()
|
||||
else:
|
||||
if isinstance(iterable, str):
|
||||
raise TypeError("Can't extend an Array with a str.")
|
||||
for item in iterable:
|
||||
self.data += self._create_element(item)
|
||||
|
||||
def insert(self, i: int, x: ElementType) -> None:
|
||||
"""Insert a new element into the Array at position i.
|
||||
|
||||
"""
|
||||
i = min(i, len(self)) # Inserting beyond len of array inserts at the end (copying standard behaviour)
|
||||
self.data.insert(self._create_element(x), i * self._dtype.length)
|
||||
|
||||
def pop(self, i: int = -1) -> ElementType:
|
||||
"""Return and remove an element of the Array.
|
||||
|
||||
Default is to return and remove the final element.
|
||||
|
||||
"""
|
||||
if len(self) == 0:
|
||||
raise IndexError("Can't pop from an empty Array.")
|
||||
x = self[i]
|
||||
del self[i]
|
||||
return x
|
||||
|
||||
def byteswap(self) -> None:
|
||||
"""Change the endianness in-place of all items in the Array.
|
||||
|
||||
If the Array format is not a whole number of bytes a ValueError will be raised.
|
||||
|
||||
"""
|
||||
if self._dtype.length % 8 != 0:
|
||||
raise ValueError(
|
||||
f"byteswap can only be used for whole-byte elements. The '{self._dtype}' format is {self._dtype.length} bits long.")
|
||||
self.data.byteswap(self.itemsize // 8)
|
||||
|
||||
def count(self, value: ElementType) -> int:
|
||||
"""Return count of Array items that equal value.
|
||||
|
||||
value -- The quantity to compare each Array element to. Type should be appropriate for the Array format.
|
||||
|
||||
For floating point types using a value of float('nan') will count the number of elements that are NaN.
|
||||
|
||||
"""
|
||||
if math.isnan(value):
|
||||
return sum(math.isnan(i) for i in self)
|
||||
else:
|
||||
return sum(i == value for i in self)
|
||||
|
||||
def tobytes(self) -> bytes:
|
||||
"""Return the Array data as a bytes object, padding with zero bits if needed.
|
||||
|
||||
Up to seven zero bits will be added at the end to byte align.
|
||||
|
||||
"""
|
||||
return self.data.tobytes()
|
||||
|
||||
def tofile(self, f: BinaryIO) -> None:
|
||||
"""Write the Array data to a file object, padding with zero bits if needed.
|
||||
|
||||
Up to seven zero bits will be added at the end to byte align.
|
||||
|
||||
"""
|
||||
self.data.tofile(f)
|
||||
|
||||
def fromfile(self, f: BinaryIO, n: Optional[int] = None) -> None:
|
||||
trailing_bit_length = len(self.data) % self._dtype.bitlength
|
||||
if trailing_bit_length != 0:
|
||||
raise ValueError(f"Cannot extend Array as its data length ({len(self.data)} bits) is not a multiple of the format length ({self._dtype.bitlength} bits).")
|
||||
|
||||
new_data = Bits(f)
|
||||
max_items = len(new_data) // self._dtype.length
|
||||
items_to_append = max_items if n is None else min(n, max_items)
|
||||
self.data += new_data[0: items_to_append * self._dtype.bitlength]
|
||||
if n is not None and items_to_append < n:
|
||||
raise EOFError(f"Only {items_to_append} were appended, not the {n} items requested.")
|
||||
|
||||
def reverse(self) -> None:
|
||||
trailing_bit_length = len(self.data) % self._dtype.length
|
||||
if trailing_bit_length != 0:
|
||||
raise ValueError(f"Cannot reverse the items in the Array as its data length ({len(self.data)} bits) is not a multiple of the format length ({self._dtype.length} bits).")
|
||||
for start_bit in range(0, len(self.data) // 2, self._dtype.length):
|
||||
start_swap_bit = len(self.data) - start_bit - self._dtype.length
|
||||
temp = self.data[start_bit: start_bit + self._dtype.length]
|
||||
self.data[start_bit: start_bit + self._dtype.length] = self.data[
|
||||
start_swap_bit: start_swap_bit + self._dtype.length]
|
||||
self.data[start_swap_bit: start_swap_bit + self._dtype.length] = temp
|
||||
|
||||
def pp(self, fmt: Optional[str] = None, width: int = 120,
|
||||
show_offset: bool = True, stream: TextIO = sys.stdout) -> None:
|
||||
"""Pretty-print the Array contents.
|
||||
|
||||
fmt -- Data format string. Defaults to current Array dtype.
|
||||
width -- Max width of printed lines in characters. Defaults to 120. A single group will always
|
||||
be printed per line even if it exceeds the max width.
|
||||
show_offset -- If True shows the element offset in the first column of each line.
|
||||
stream -- A TextIO object with a write() method. Defaults to sys.stdout.
|
||||
|
||||
"""
|
||||
colour = Colour(not options.no_color)
|
||||
sep = ' '
|
||||
dtype2 = None
|
||||
tidy_fmt = None
|
||||
if fmt is None:
|
||||
fmt = self.dtype
|
||||
dtype1 = self.dtype
|
||||
tidy_fmt = "dtype='" + colour.purple + str(self.dtype) + "'" + colour.off
|
||||
else:
|
||||
token_list = utils.preprocess_tokens(fmt)
|
||||
if len(token_list) not in [1, 2]:
|
||||
raise ValueError(f"Only one or two tokens can be used in an Array.pp() format - '{fmt}' has {len(token_list)} tokens.")
|
||||
name1, length1 = utils.parse_name_length_token(token_list[0])
|
||||
dtype1 = Dtype(name1, length1)
|
||||
if len(token_list) == 2:
|
||||
name2, length2 = utils.parse_name_length_token(token_list[1])
|
||||
dtype2 = Dtype(name2, length2)
|
||||
|
||||
token_length = dtype1.bitlength
|
||||
if dtype2 is not None:
|
||||
# For two types we're OK as long as they don't have different lengths given.
|
||||
if dtype1.bitlength is not None and dtype2.bitlength is not None and dtype1.bitlength != dtype2.bitlength:
|
||||
raise ValueError(f"Two different format lengths specified ('{fmt}'). Either specify just one, or two the same length.")
|
||||
if token_length is None:
|
||||
token_length = dtype2.bitlength
|
||||
if token_length is None:
|
||||
token_length = self.itemsize
|
||||
|
||||
trailing_bit_length = len(self.data) % token_length
|
||||
format_sep = " : " # String to insert on each line between multiple formats
|
||||
if tidy_fmt is None:
|
||||
tidy_fmt = colour.purple + str(dtype1) + colour.off
|
||||
if dtype2 is not None:
|
||||
tidy_fmt += ', ' + colour.blue + str(dtype2) + colour.off
|
||||
tidy_fmt = "fmt='" + tidy_fmt + "'"
|
||||
data = self.data if trailing_bit_length == 0 else self.data[0: -trailing_bit_length]
|
||||
length = len(self.data) // token_length
|
||||
len_str = colour.green + str(length) + colour.off
|
||||
stream.write(f"<{self.__class__.__name__} {tidy_fmt}, length={len_str}, itemsize={token_length} bits, total data size={(len(self.data) + 7) // 8} bytes> [\n")
|
||||
data._pp(dtype1, dtype2, token_length, width, sep, format_sep, show_offset, stream, False, token_length)
|
||||
stream.write("]")
|
||||
if trailing_bit_length != 0:
|
||||
stream.write(" + trailing_bits = " + str(self.data[-trailing_bit_length:]))
|
||||
stream.write("\n")
|
||||
|
||||
def equals(self, other: Any) -> bool:
|
||||
"""Return True if format and all Array items are equal."""
|
||||
if isinstance(other, Array):
|
||||
if self._dtype.length != other._dtype.length:
|
||||
return False
|
||||
if self._dtype.name != other._dtype.name:
|
||||
return False
|
||||
if self.data != other.data:
|
||||
return False
|
||||
return True
|
||||
elif isinstance(other, array.array):
|
||||
# Assume we are comparing with an array type
|
||||
if self.trailing_bits:
|
||||
return False
|
||||
# array's itemsize is in bytes, not bits.
|
||||
if self.itemsize != other.itemsize * 8:
|
||||
return False
|
||||
if len(self) != len(other):
|
||||
return False
|
||||
if self.tolist() != other.tolist():
|
||||
return False
|
||||
return True
|
||||
return False
|
||||
|
||||
def __iter__(self) -> Iterable[ElementType]:
|
||||
start = 0
|
||||
for _ in range(len(self)):
|
||||
yield self._dtype.read_fn(self.data, start=start)
|
||||
start += self._dtype.length
|
||||
|
||||
def __copy__(self) -> Array:
|
||||
a_copy = self.__class__(self._dtype)
|
||||
a_copy.data = copy.copy(self.data)
|
||||
return a_copy
|
||||
|
||||
def _apply_op_to_all_elements(self, op, value: Union[int, float, None], is_comparison: bool = False) -> Array:
|
||||
"""Apply op with value to each element of the Array and return a new Array"""
|
||||
new_array = self.__class__('bool' if is_comparison else self._dtype)
|
||||
new_data = BitArray()
|
||||
failures = index = 0
|
||||
msg = ''
|
||||
if value is not None:
|
||||
def partial_op(a):
|
||||
return op(a, value)
|
||||
else:
|
||||
def partial_op(a):
|
||||
return op(a)
|
||||
for i in range(len(self)):
|
||||
v = self._dtype.read_fn(self.data, start=self._dtype.length * i)
|
||||
try:
|
||||
new_data.append(new_array._create_element(partial_op(v)))
|
||||
except (CreationError, ZeroDivisionError, ValueError) as e:
|
||||
if failures == 0:
|
||||
msg = str(e)
|
||||
index = i
|
||||
failures += 1
|
||||
if failures != 0:
|
||||
raise ValueError(f"Applying operator '{op.__name__}' to Array caused {failures} errors. "
|
||||
f'First error at index {index} was: "{msg}"')
|
||||
new_array.data = new_data
|
||||
return new_array
|
||||
|
||||
def _apply_op_to_all_elements_inplace(self, op, value: Union[int, float]) -> Array:
|
||||
"""Apply op with value to each element of the Array in place."""
|
||||
# This isn't really being done in-place, but it's simpler and faster for now?
|
||||
new_data = BitArray()
|
||||
failures = index = 0
|
||||
msg = ''
|
||||
for i in range(len(self)):
|
||||
v = self._dtype.read_fn(self.data, start=self._dtype.length * i)
|
||||
try:
|
||||
new_data.append(self._create_element(op(v, value)))
|
||||
except (CreationError, ZeroDivisionError, ValueError) as e:
|
||||
if failures == 0:
|
||||
msg = str(e)
|
||||
index = i
|
||||
failures += 1
|
||||
if failures != 0:
|
||||
raise ValueError(f"Applying operator '{op.__name__}' to Array caused {failures} errors. "
|
||||
f'First error at index {index} was: "{msg}"')
|
||||
self.data = new_data
|
||||
return self
|
||||
|
||||
def _apply_bitwise_op_to_all_elements(self, op, value: BitsType) -> Array:
|
||||
"""Apply op with value to each element of the Array as an unsigned integer and return a new Array"""
|
||||
a_copy = self[:]
|
||||
a_copy._apply_bitwise_op_to_all_elements_inplace(op, value)
|
||||
return a_copy
|
||||
|
||||
def _apply_bitwise_op_to_all_elements_inplace(self, op, value: BitsType) -> Array:
|
||||
"""Apply op with value to each element of the Array as an unsigned integer in place."""
|
||||
value = BitArray._create_from_bitstype(value)
|
||||
if len(value) != self._dtype.length:
|
||||
raise ValueError(f"Bitwise op needs a bitstring of length {self._dtype.length} to match format {self._dtype}.")
|
||||
for start in range(0, len(self) * self._dtype.length, self._dtype.length):
|
||||
self.data[start: start + self._dtype.length] = op(self.data[start: start + self._dtype.length], value)
|
||||
return self
|
||||
|
||||
def _apply_op_between_arrays(self, op, other: Array, is_comparison: bool = False) -> Array:
|
||||
if len(self) != len(other):
|
||||
msg = f"Cannot operate element-wise on Arrays with different lengths ({len(self)} and {len(other)})."
|
||||
if op in [operator.add, operator.iadd]:
|
||||
msg += " Use extend() method to concatenate Arrays."
|
||||
if op in [operator.eq, operator.ne]:
|
||||
msg += " Use equals() method to compare Arrays for a single boolean result."
|
||||
raise ValueError(msg)
|
||||
if is_comparison:
|
||||
new_type = dtype_register.get_dtype('bool', 1)
|
||||
else:
|
||||
new_type = self._promotetype(self._dtype, other._dtype)
|
||||
new_array = self.__class__(new_type)
|
||||
new_data = BitArray()
|
||||
failures = index = 0
|
||||
msg = ''
|
||||
for i in range(len(self)):
|
||||
a = self._dtype.read_fn(self.data, start=self._dtype.length * i)
|
||||
b = other._dtype.read_fn(other.data, start=other._dtype.length * i)
|
||||
try:
|
||||
new_data.append(new_array._create_element(op(a, b)))
|
||||
except (CreationError, ValueError, ZeroDivisionError) as e:
|
||||
if failures == 0:
|
||||
msg = str(e)
|
||||
index = i
|
||||
failures += 1
|
||||
if failures != 0:
|
||||
raise ValueError(f"Applying operator '{op.__name__}' between Arrays caused {failures} errors. "
|
||||
f'First error at index {index} was: "{msg}"')
|
||||
new_array.data = new_data
|
||||
return new_array
|
||||
|
||||
@classmethod
|
||||
def _promotetype(cls, type1: Dtype, type2: Dtype) -> Dtype:
|
||||
"""When combining types which one wins?
|
||||
|
||||
1. We only deal with types representing floats or integers.
|
||||
2. One of the two types gets returned. We never create a new one.
|
||||
3. Floating point types always win against integer types.
|
||||
4. Signed integer types always win against unsigned integer types.
|
||||
5. Longer types win against shorter types.
|
||||
6. In a tie the first type wins against the second type.
|
||||
|
||||
"""
|
||||
def is_float(x): return x.return_type is float
|
||||
def is_int(x): return x.return_type is int or x.return_type is bool
|
||||
if is_float(type1) + is_int(type1) + is_float(type2) + is_int(type2) != 2:
|
||||
raise ValueError(f"Only integer and floating point types can be combined - not '{type1}' and '{type2}'.")
|
||||
# If same type choose the widest
|
||||
if type1.name == type2.name:
|
||||
return type1 if type1.length > type2.length else type2
|
||||
# We choose floats above integers, irrespective of the widths
|
||||
if is_float(type1) and is_int(type2):
|
||||
return type1
|
||||
if is_int(type1) and is_float(type2):
|
||||
return type2
|
||||
if is_float(type1) and is_float(type2):
|
||||
return type2 if type2.length > type1.length else type1
|
||||
assert is_int(type1) and is_int(type2)
|
||||
if type1.is_signed and not type2.is_signed:
|
||||
return type1
|
||||
if type2.is_signed and not type1.is_signed:
|
||||
return type2
|
||||
return type2 if type2.length > type1.length else type1
|
||||
|
||||
# Operators between Arrays or an Array and scalar value
|
||||
|
||||
def __add__(self, other: Union[int, float, Array]) -> Array:
|
||||
"""Add int or float to all elements."""
|
||||
if isinstance(other, Array):
|
||||
return self._apply_op_between_arrays(operator.add, other)
|
||||
return self._apply_op_to_all_elements(operator.add, other)
|
||||
|
||||
def __iadd__(self, other: Union[int, float, Array]) -> Array:
|
||||
if isinstance(other, Array):
|
||||
return self._apply_op_between_arrays(operator.add, other)
|
||||
return self._apply_op_to_all_elements_inplace(operator.add, other)
|
||||
|
||||
def __isub__(self, other: Union[int, float, Array]) -> Array:
|
||||
if isinstance(other, Array):
|
||||
return self._apply_op_between_arrays(operator.sub, other)
|
||||
return self._apply_op_to_all_elements_inplace(operator.sub, other)
|
||||
|
||||
def __sub__(self, other: Union[int, float, Array]) -> Array:
|
||||
if isinstance(other, Array):
|
||||
return self._apply_op_between_arrays(operator.sub, other)
|
||||
return self._apply_op_to_all_elements(operator.sub, other)
|
||||
|
||||
def __mul__(self, other: Union[int, float, Array]) -> Array:
|
||||
if isinstance(other, Array):
|
||||
return self._apply_op_between_arrays(operator.mul, other)
|
||||
return self._apply_op_to_all_elements(operator.mul, other)
|
||||
|
||||
def __imul__(self, other: Union[int, float, Array]) -> Array:
|
||||
if isinstance(other, Array):
|
||||
return self._apply_op_between_arrays(operator.mul, other)
|
||||
return self._apply_op_to_all_elements_inplace(operator.mul, other)
|
||||
|
||||
def __floordiv__(self, other: Union[int, float, Array]) -> Array:
|
||||
if isinstance(other, Array):
|
||||
return self._apply_op_between_arrays(operator.floordiv, other)
|
||||
return self._apply_op_to_all_elements(operator.floordiv, other)
|
||||
|
||||
def __ifloordiv__(self, other: Union[int, float, Array]) -> Array:
|
||||
if isinstance(other, Array):
|
||||
return self._apply_op_between_arrays(operator.floordiv, other)
|
||||
return self._apply_op_to_all_elements_inplace(operator.floordiv, other)
|
||||
|
||||
def __truediv__(self, other: Union[int, float, Array]) -> Array:
|
||||
if isinstance(other, Array):
|
||||
return self._apply_op_between_arrays(operator.truediv, other)
|
||||
return self._apply_op_to_all_elements(operator.truediv, other)
|
||||
|
||||
def __itruediv__(self, other: Union[int, float, Array]) -> Array:
|
||||
if isinstance(other, Array):
|
||||
return self._apply_op_between_arrays(operator.truediv, other)
|
||||
return self._apply_op_to_all_elements_inplace(operator.truediv, other)
|
||||
|
||||
def __rshift__(self, other: Union[int, Array]) -> Array:
|
||||
if isinstance(other, Array):
|
||||
return self._apply_op_between_arrays(operator.rshift, other)
|
||||
return self._apply_op_to_all_elements(operator.rshift, other)
|
||||
|
||||
def __lshift__(self, other: Union[int, Array]) -> Array:
|
||||
if isinstance(other, Array):
|
||||
return self._apply_op_between_arrays(operator.lshift, other)
|
||||
return self._apply_op_to_all_elements(operator.lshift, other)
|
||||
|
||||
def __irshift__(self, other: Union[int, Array]) -> Array:
|
||||
if isinstance(other, Array):
|
||||
return self._apply_op_between_arrays(operator.rshift, other)
|
||||
return self._apply_op_to_all_elements_inplace(operator.rshift, other)
|
||||
|
||||
def __ilshift__(self, other: Union[int, Array]) -> Array:
|
||||
if isinstance(other, Array):
|
||||
return self._apply_op_between_arrays(operator.lshift, other)
|
||||
return self._apply_op_to_all_elements_inplace(operator.lshift, other)
|
||||
|
||||
def __mod__(self, other: Union[int, Array]) -> Array:
|
||||
if isinstance(other, Array):
|
||||
return self._apply_op_between_arrays(operator.mod, other)
|
||||
return self._apply_op_to_all_elements(operator.mod, other)
|
||||
|
||||
def __imod__(self, other: Union[int, Array]) -> Array:
|
||||
if isinstance(other, Array):
|
||||
return self._apply_op_between_arrays(operator.mod, other)
|
||||
return self._apply_op_to_all_elements_inplace(operator.mod, other)
|
||||
|
||||
# Bitwise operators
|
||||
|
||||
def __and__(self, other: BitsType) -> Array:
|
||||
return self._apply_bitwise_op_to_all_elements(operator.iand, other)
|
||||
|
||||
def __iand__(self, other: BitsType) -> Array:
|
||||
return self._apply_bitwise_op_to_all_elements_inplace(operator.iand, other)
|
||||
|
||||
def __or__(self, other: BitsType) -> Array:
|
||||
return self._apply_bitwise_op_to_all_elements(operator.ior, other)
|
||||
|
||||
def __ior__(self, other: BitsType) -> Array:
|
||||
return self._apply_bitwise_op_to_all_elements_inplace(operator.ior, other)
|
||||
|
||||
def __xor__(self, other: BitsType) -> Array:
|
||||
return self._apply_bitwise_op_to_all_elements(operator.ixor, other)
|
||||
|
||||
def __ixor__(self, other: BitsType) -> Array:
|
||||
return self._apply_bitwise_op_to_all_elements_inplace(operator.ixor, other)
|
||||
|
||||
# Reverse operators between a scalar value and an Array
|
||||
|
||||
def __rmul__(self, other: Union[int, float]) -> Array:
|
||||
return self._apply_op_to_all_elements(operator.mul, other)
|
||||
|
||||
def __radd__(self, other: Union[int, float]) -> Array:
|
||||
return self._apply_op_to_all_elements(operator.add, other)
|
||||
|
||||
def __rsub__(self, other: Union[int, float]) -> Array:
|
||||
# i - A == (-A) + i
|
||||
neg = self._apply_op_to_all_elements(operator.neg, None)
|
||||
return neg._apply_op_to_all_elements(operator.add, other)
|
||||
|
||||
# Reverse operators between a scalar and something that can be a BitArray.
|
||||
|
||||
def __rand__(self, other: BitsType) -> Array:
|
||||
return self._apply_bitwise_op_to_all_elements(operator.iand, other)
|
||||
|
||||
def __ror__(self, other: BitsType) -> Array:
|
||||
return self._apply_bitwise_op_to_all_elements(operator.ior, other)
|
||||
|
||||
def __rxor__(self, other: BitsType) -> Array:
|
||||
return self._apply_bitwise_op_to_all_elements(operator.ixor, other)
|
||||
|
||||
# Comparison operators
|
||||
|
||||
def __lt__(self, other: Union[int, float, Array]) -> Array:
|
||||
if isinstance(other, Array):
|
||||
return self._apply_op_between_arrays(operator.lt, other, is_comparison=True)
|
||||
return self._apply_op_to_all_elements(operator.lt, other, is_comparison=True)
|
||||
|
||||
def __gt__(self, other: Union[int, float, Array]) -> Array:
|
||||
if isinstance(other, Array):
|
||||
return self._apply_op_between_arrays(operator.gt, other, is_comparison=True)
|
||||
return self._apply_op_to_all_elements(operator.gt, other, is_comparison=True)
|
||||
|
||||
def __ge__(self, other: Union[int, float, Array]) -> Array:
|
||||
if isinstance(other, Array):
|
||||
return self._apply_op_between_arrays(operator.ge, other, is_comparison=True)
|
||||
return self._apply_op_to_all_elements(operator.ge, other, is_comparison=True)
|
||||
|
||||
def __le__(self, other: Union[int, float, Array]) -> Array:
|
||||
if isinstance(other, Array):
|
||||
return self._apply_op_between_arrays(operator.le, other, is_comparison=True)
|
||||
return self._apply_op_to_all_elements(operator.le, other, is_comparison=True)
|
||||
|
||||
def _eq_ne(self, op, other: Any) -> Array:
|
||||
if isinstance(other, (int, float, str, Bits)):
|
||||
return self._apply_op_to_all_elements(op, other, is_comparison=True)
|
||||
try:
|
||||
other = self.__class__(self.dtype, other)
|
||||
except:
|
||||
return NotImplemented
|
||||
finally:
|
||||
return self._apply_op_between_arrays(op, other, is_comparison=True)
|
||||
|
||||
def __eq__(self, other: Any) -> Array:
|
||||
return self._eq_ne(operator.eq, other)
|
||||
|
||||
def __ne__(self, other: Any) -> Array:
|
||||
return self._eq_ne(operator.ne, other)
|
||||
|
||||
# Unary operators
|
||||
|
||||
def __neg__(self):
|
||||
return self._apply_op_to_all_elements(operator.neg, None)
|
||||
|
||||
def __abs__(self):
|
||||
return self._apply_op_to_all_elements(operator.abs, None)
|
||||
576
code/.venv/lib/python3.12/site-packages/bitstring/bitarray_.py
Normal file
576
code/.venv/lib/python3.12/site-packages/bitstring/bitarray_.py
Normal file
@@ -0,0 +1,576 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import copy
|
||||
import numbers
|
||||
import re
|
||||
from collections import abc
|
||||
from typing import Union, List, Iterable, Any, Optional
|
||||
from bitstring import utils
|
||||
from bitstring.exceptions import CreationError, Error
|
||||
from bitstring.bits import Bits, BitsType, TBits
|
||||
|
||||
import bitstring.dtypes
|
||||
|
||||
|
||||
class BitArray(Bits):
|
||||
"""A container holding a mutable sequence of bits.
|
||||
|
||||
Subclass of the immutable Bits class. Inherits all of its
|
||||
methods (except __hash__) and adds mutating methods.
|
||||
|
||||
Mutating methods:
|
||||
|
||||
append() -- Append a bitstring.
|
||||
byteswap() -- Change byte endianness in-place.
|
||||
clear() -- Remove all bits from the bitstring.
|
||||
insert() -- Insert a bitstring.
|
||||
invert() -- Flip bit(s) between one and zero.
|
||||
overwrite() -- Overwrite a section with a new bitstring.
|
||||
prepend() -- Prepend a bitstring.
|
||||
replace() -- Replace occurrences of one bitstring with another.
|
||||
reverse() -- Reverse bits in-place.
|
||||
rol() -- Rotate bits to the left.
|
||||
ror() -- Rotate bits to the right.
|
||||
set() -- Set bit(s) to 1 or 0.
|
||||
|
||||
Methods inherited from Bits:
|
||||
|
||||
all() -- Check if all specified bits are set to 1 or 0.
|
||||
any() -- Check if any of specified bits are set to 1 or 0.
|
||||
copy() -- Return a copy of the bitstring.
|
||||
count() -- Count the number of bits set to 1 or 0.
|
||||
cut() -- Create generator of constant sized chunks.
|
||||
endswith() -- Return whether the bitstring ends with a sub-string.
|
||||
find() -- Find a sub-bitstring in the current bitstring.
|
||||
findall() -- Find all occurrences of a sub-bitstring in the current bitstring.
|
||||
fromstring() -- Create a bitstring from a formatted string.
|
||||
join() -- Join bitstrings together using current bitstring.
|
||||
pp() -- Pretty print the bitstring.
|
||||
rfind() -- Seek backwards to find a sub-bitstring.
|
||||
split() -- Create generator of chunks split by a delimiter.
|
||||
startswith() -- Return whether the bitstring starts with a sub-bitstring.
|
||||
tobitarray() -- Return bitstring as a bitarray from the bitarray package.
|
||||
tobytes() -- Return bitstring as bytes, padding if needed.
|
||||
tofile() -- Write bitstring to file, padding if needed.
|
||||
unpack() -- Interpret bits using format string.
|
||||
|
||||
Special methods:
|
||||
|
||||
Mutating operators are available: [], <<=, >>=, +=, *=, &=, |= and ^=
|
||||
in addition to the inherited [], ==, !=, +, *, ~, <<, >>, &, | and ^.
|
||||
|
||||
Properties:
|
||||
|
||||
[GENERATED_PROPERTY_DESCRIPTIONS]
|
||||
|
||||
len -- Length of the bitstring in bits.
|
||||
|
||||
"""
|
||||
|
||||
__slots__ = ()
|
||||
|
||||
# As BitArray objects are mutable, we shouldn't allow them to be hashed.
|
||||
__hash__: None = None
|
||||
|
||||
def __init__(self, auto: Optional[Union[BitsType, int]] = None, /, length: Optional[int] = None,
|
||||
offset: Optional[int] = None, **kwargs) -> None:
|
||||
"""Either specify an 'auto' initialiser:
|
||||
A string of comma separated tokens, an integer, a file object,
|
||||
a bytearray, a boolean iterable or another bitstring.
|
||||
|
||||
Or initialise via **kwargs with one (and only one) of:
|
||||
bin -- binary string representation, e.g. '0b001010'.
|
||||
hex -- hexadecimal string representation, e.g. '0x2ef'
|
||||
oct -- octal string representation, e.g. '0o777'.
|
||||
bytes -- raw data as a bytes object, for example read from a binary file.
|
||||
int -- a signed integer.
|
||||
uint -- an unsigned integer.
|
||||
float / floatbe -- a big-endian floating point number.
|
||||
bool -- a boolean (True or False).
|
||||
se -- a signed exponential-Golomb code.
|
||||
ue -- an unsigned exponential-Golomb code.
|
||||
sie -- a signed interleaved exponential-Golomb code.
|
||||
uie -- an unsigned interleaved exponential-Golomb code.
|
||||
floatle -- a little-endian floating point number.
|
||||
floatne -- a native-endian floating point number.
|
||||
bfloat / bfloatbe - a big-endian bfloat format 16-bit floating point number.
|
||||
bfloatle -- a little-endian bfloat format 16-bit floating point number.
|
||||
bfloatne -- a native-endian bfloat format 16-bit floating point number.
|
||||
intbe -- a signed big-endian whole byte integer.
|
||||
intle -- a signed little-endian whole byte integer.
|
||||
intne -- a signed native-endian whole byte integer.
|
||||
uintbe -- an unsigned big-endian whole byte integer.
|
||||
uintle -- an unsigned little-endian whole byte integer.
|
||||
uintne -- an unsigned native-endian whole byte integer.
|
||||
filename -- the path of a file which will be opened in binary read-only mode.
|
||||
|
||||
Other keyword arguments:
|
||||
length -- length of the bitstring in bits, if needed and appropriate.
|
||||
It must be supplied for all integer and float initialisers.
|
||||
offset -- bit offset to the data. These offset bits are
|
||||
ignored and this is intended for use when
|
||||
initialising using 'bytes' or 'filename'.
|
||||
|
||||
"""
|
||||
if self._bitstore.immutable:
|
||||
self._bitstore = self._bitstore._copy()
|
||||
self._bitstore.immutable = False
|
||||
|
||||
def copy(self: TBits) -> TBits:
|
||||
"""Return a copy of the bitstring."""
|
||||
return self.__copy__()
|
||||
|
||||
def __setattr__(self, attribute, value) -> None:
|
||||
try:
|
||||
# First try the ordinary attribute setter
|
||||
super().__setattr__(attribute, value)
|
||||
except AttributeError:
|
||||
dtype = bitstring.dtypes.Dtype(attribute)
|
||||
x = object.__new__(Bits)
|
||||
if (set_fn := dtype.set_fn) is None:
|
||||
raise AttributeError(f"Cannot set attribute '{attribute}' as it does not have a set_fn.")
|
||||
set_fn(x, value)
|
||||
if len(x) != dtype.bitlength:
|
||||
raise CreationError(f"Can't initialise with value of length {len(x)} bits, "
|
||||
f"as attribute has length of {dtype.bitlength} bits.")
|
||||
self._bitstore = x._bitstore
|
||||
return
|
||||
|
||||
def __iadd__(self, bs: BitsType) -> BitArray:
|
||||
"""Append bs to current bitstring. Return self.
|
||||
|
||||
bs -- the bitstring to append.
|
||||
|
||||
"""
|
||||
self._append(bs)
|
||||
return self
|
||||
|
||||
def __copy__(self) -> BitArray:
|
||||
"""Return a new copy of the BitArray."""
|
||||
s_copy = BitArray()
|
||||
s_copy._bitstore = self._bitstore._copy()
|
||||
assert s_copy._bitstore.immutable is False
|
||||
return s_copy
|
||||
|
||||
def _setitem_int(self, key: int, value: Union[BitsType, int]) -> None:
|
||||
if isinstance(value, numbers.Integral):
|
||||
if value == 0:
|
||||
self._bitstore[key] = 0
|
||||
return
|
||||
if value in (1, -1):
|
||||
self._bitstore[key] = 1
|
||||
return
|
||||
raise ValueError(f"Cannot set a single bit with integer {value}.")
|
||||
try:
|
||||
value = self._create_from_bitstype(value)
|
||||
except TypeError:
|
||||
raise TypeError(f"Bitstring, integer or string expected. Got {type(value)}.")
|
||||
positive_key = key + len(self) if key < 0 else key
|
||||
if positive_key < 0 or positive_key >= len(self._bitstore):
|
||||
raise IndexError(f"Bit position {key} out of range.")
|
||||
self._bitstore[positive_key: positive_key + 1] = value._bitstore
|
||||
|
||||
def _setitem_slice(self, key: slice, value: BitsType) -> None:
|
||||
if isinstance(value, numbers.Integral):
|
||||
value = int(value)
|
||||
if key.step not in [None, -1, 1]:
|
||||
if value in [0, 1]:
|
||||
self.set(value, range(*key.indices(len(self))))
|
||||
return
|
||||
else:
|
||||
raise ValueError("Can't assign an integer except 0 or 1 to a slice with a step value.")
|
||||
# To find the length we first get the slice
|
||||
s = self._bitstore.getslice(key.start, key.stop)
|
||||
length = len(s)
|
||||
# Now create an int of the correct length
|
||||
if value >= 0:
|
||||
value = self.__class__(uint=value, length=length)
|
||||
else:
|
||||
value = self.__class__(int=value, length=length)
|
||||
else:
|
||||
try:
|
||||
value = self._create_from_bitstype(value)
|
||||
except TypeError:
|
||||
raise TypeError(f"Bitstring, integer or string expected. Got {type(value)}.")
|
||||
self._bitstore.__setitem__(key, value._bitstore)
|
||||
|
||||
def __setitem__(self, key: Union[slice, int], value: BitsType) -> None:
|
||||
if isinstance(key, numbers.Integral):
|
||||
self._setitem_int(int(key), value)
|
||||
else:
|
||||
self._setitem_slice(key, value)
|
||||
|
||||
def __delitem__(self, key: Union[slice, int]) -> None:
|
||||
"""Delete item or range.
|
||||
|
||||
>>> a = BitArray('0x001122')
|
||||
>>> del a[8:16]
|
||||
>>> print a
|
||||
0x0022
|
||||
|
||||
"""
|
||||
self._bitstore.__delitem__(key)
|
||||
return
|
||||
|
||||
def __ilshift__(self: TBits, n: int) -> TBits:
|
||||
"""Shift bits by n to the left in place. Return self.
|
||||
|
||||
n -- the number of bits to shift. Must be >= 0.
|
||||
|
||||
"""
|
||||
if n < 0:
|
||||
raise ValueError("Cannot shift by a negative amount.")
|
||||
if not len(self):
|
||||
raise ValueError("Cannot shift an empty bitstring.")
|
||||
if not n:
|
||||
return self
|
||||
n = min(n, len(self))
|
||||
return self._ilshift(n)
|
||||
|
||||
def __irshift__(self: TBits, n: int) -> TBits:
|
||||
"""Shift bits by n to the right in place. Return self.
|
||||
|
||||
n -- the number of bits to shift. Must be >= 0.
|
||||
|
||||
"""
|
||||
if n < 0:
|
||||
raise ValueError("Cannot shift by a negative amount.")
|
||||
if not len(self):
|
||||
raise ValueError("Cannot shift an empty bitstring.")
|
||||
if not n:
|
||||
return self
|
||||
n = min(n, len(self))
|
||||
return self._irshift(n)
|
||||
|
||||
def __imul__(self: TBits, n: int) -> TBits:
|
||||
"""Concatenate n copies of self in place. Return self.
|
||||
|
||||
Called for expressions of the form 'a *= 3'.
|
||||
n -- The number of concatenations. Must be >= 0.
|
||||
|
||||
"""
|
||||
if n < 0:
|
||||
raise ValueError("Cannot multiply by a negative integer.")
|
||||
return self._imul(n)
|
||||
|
||||
def __ior__(self: TBits, bs: BitsType) -> TBits:
|
||||
bs = self._create_from_bitstype(bs)
|
||||
self._bitstore |= bs._bitstore
|
||||
return self
|
||||
|
||||
def __iand__(self: TBits, bs: BitsType) -> TBits:
|
||||
bs = self._create_from_bitstype(bs)
|
||||
self._bitstore &= bs._bitstore
|
||||
return self
|
||||
|
||||
def __ixor__(self: TBits, bs: BitsType) -> TBits:
|
||||
bs = self._create_from_bitstype(bs)
|
||||
self._bitstore ^= bs._bitstore
|
||||
return self
|
||||
|
||||
def _replace(self, old: Bits, new: Bits, start: int, end: int, count: int, bytealigned: Optional[bool]) -> int:
|
||||
if bytealigned is None:
|
||||
bytealigned = bitstring.options.bytealigned
|
||||
# First find all the places where we want to do the replacements
|
||||
starting_points: List[int] = []
|
||||
for x in self.findall(old, start, end, bytealigned=bytealigned):
|
||||
if not starting_points:
|
||||
starting_points.append(x)
|
||||
elif x >= starting_points[-1] + len(old):
|
||||
# Can only replace here if it hasn't already been replaced!
|
||||
starting_points.append(x)
|
||||
if count != 0 and len(starting_points) == count:
|
||||
break
|
||||
if not starting_points:
|
||||
return 0
|
||||
replacement_list = [self._bitstore.getslice(0, starting_points[0])]
|
||||
for i in range(len(starting_points) - 1):
|
||||
replacement_list.append(new._bitstore)
|
||||
replacement_list.append(
|
||||
self._bitstore.getslice(starting_points[i] + len(old), starting_points[i + 1]))
|
||||
# Final replacement
|
||||
replacement_list.append(new._bitstore)
|
||||
replacement_list.append(self._bitstore.getslice(starting_points[-1] + len(old), None))
|
||||
if bitstring.options.lsb0:
|
||||
# Addition of bitarray is always on the right, so assemble from other end
|
||||
replacement_list.reverse()
|
||||
self._bitstore.clear()
|
||||
for r in replacement_list:
|
||||
self._bitstore += r
|
||||
return len(starting_points)
|
||||
|
||||
def replace(self, old: BitsType, new: BitsType, start: Optional[int] = None, end: Optional[int] = None,
|
||||
count: Optional[int] = None, bytealigned: Optional[bool] = None) -> int:
|
||||
"""Replace all occurrences of old with new in place.
|
||||
|
||||
Returns number of replacements made.
|
||||
|
||||
old -- The bitstring to replace.
|
||||
new -- The replacement bitstring.
|
||||
start -- Any occurrences that start before this will not be replaced.
|
||||
Defaults to 0.
|
||||
end -- Any occurrences that finish after this will not be replaced.
|
||||
Defaults to len(self).
|
||||
count -- The maximum number of replacements to make. Defaults to
|
||||
replace all occurrences.
|
||||
bytealigned -- If True replacements will only be made on byte
|
||||
boundaries.
|
||||
|
||||
Raises ValueError if old is empty or if start or end are
|
||||
out of range.
|
||||
|
||||
"""
|
||||
if count == 0:
|
||||
return 0
|
||||
old = self._create_from_bitstype(old)
|
||||
new = self._create_from_bitstype(new)
|
||||
if len(old) == 0:
|
||||
raise ValueError("Empty bitstring cannot be replaced.")
|
||||
start, end = self._validate_slice(start, end)
|
||||
|
||||
if new is self:
|
||||
# Prevent self assignment woes
|
||||
new = copy.copy(self)
|
||||
return self._replace(old, new, start, end, 0 if count is None else count, bytealigned)
|
||||
|
||||
def insert(self, bs: BitsType, pos: int) -> None:
|
||||
"""Insert bs at bit position pos.
|
||||
|
||||
bs -- The bitstring to insert.
|
||||
pos -- The bit position to insert at.
|
||||
|
||||
Raises ValueError if pos < 0 or pos > len(self).
|
||||
|
||||
"""
|
||||
bs = self._create_from_bitstype(bs)
|
||||
if len(bs) == 0:
|
||||
return
|
||||
if bs is self:
|
||||
bs = self._copy()
|
||||
if pos < 0:
|
||||
pos += len(self)
|
||||
if not 0 <= pos <= len(self):
|
||||
raise ValueError("Invalid insert position.")
|
||||
self._insert(bs, pos)
|
||||
|
||||
def overwrite(self, bs: BitsType, pos: int) -> None:
|
||||
"""Overwrite with bs at bit position pos.
|
||||
|
||||
bs -- The bitstring to overwrite with.
|
||||
pos -- The bit position to begin overwriting from.
|
||||
|
||||
Raises ValueError if pos < 0 or pos > len(self).
|
||||
|
||||
"""
|
||||
bs = self._create_from_bitstype(bs)
|
||||
if len(bs) == 0:
|
||||
return
|
||||
if pos < 0:
|
||||
pos += len(self)
|
||||
if pos < 0 or pos > len(self):
|
||||
raise ValueError("Overwrite starts outside boundary of bitstring.")
|
||||
self._overwrite(bs, pos)
|
||||
|
||||
def append(self, bs: BitsType) -> None:
|
||||
"""Append a bitstring to the current bitstring.
|
||||
|
||||
bs -- The bitstring to append.
|
||||
|
||||
"""
|
||||
self._append(bs)
|
||||
|
||||
def prepend(self, bs: BitsType) -> None:
|
||||
"""Prepend a bitstring to the current bitstring.
|
||||
|
||||
bs -- The bitstring to prepend.
|
||||
|
||||
"""
|
||||
self._prepend(bs)
|
||||
|
||||
def _append_msb0(self, bs: BitsType) -> None:
|
||||
self._addright(self._create_from_bitstype(bs))
|
||||
|
||||
def _append_lsb0(self, bs: BitsType) -> None:
|
||||
bs = self._create_from_bitstype(bs)
|
||||
self._addleft(bs)
|
||||
|
||||
def reverse(self, start: Optional[int] = None, end: Optional[int] = None) -> None:
|
||||
"""Reverse bits in-place.
|
||||
|
||||
start -- Position of first bit to reverse. Defaults to 0.
|
||||
end -- One past the position of the last bit to reverse.
|
||||
Defaults to len(self).
|
||||
|
||||
Using on an empty bitstring will have no effect.
|
||||
|
||||
Raises ValueError if start < 0, end > len(self) or end < start.
|
||||
|
||||
"""
|
||||
start, end = self._validate_slice(start, end)
|
||||
if start == 0 and end == len(self):
|
||||
self._bitstore.reverse()
|
||||
return
|
||||
s = self._slice(start, end)
|
||||
s._bitstore.reverse()
|
||||
self[start:end] = s
|
||||
|
||||
def set(self, value: Any, pos: Optional[Union[int, Iterable[int]]] = None) -> None:
|
||||
"""Set one or many bits to 1 or 0.
|
||||
|
||||
value -- If bool(value) is True bits are set to 1, otherwise they are set to 0.
|
||||
pos -- Either a single bit position or an iterable of bit positions.
|
||||
Negative numbers are treated in the same way as slice indices.
|
||||
Defaults to the entire bitstring.
|
||||
|
||||
Raises IndexError if pos < -len(self) or pos >= len(self).
|
||||
|
||||
"""
|
||||
if pos is None:
|
||||
# Set all bits to either 1 or 0
|
||||
self._setint(-1 if value else 0)
|
||||
return
|
||||
if not isinstance(pos, abc.Iterable):
|
||||
pos = (pos,)
|
||||
v = 1 if value else 0
|
||||
if isinstance(pos, range):
|
||||
self._bitstore.__setitem__(slice(pos.start, pos.stop, pos.step), v)
|
||||
return
|
||||
for p in pos:
|
||||
self._bitstore[p] = v
|
||||
|
||||
def invert(self, pos: Optional[Union[Iterable[int], int]] = None) -> None:
|
||||
"""Invert one or many bits from 0 to 1 or vice versa.
|
||||
|
||||
pos -- Either a single bit position or an iterable of bit positions.
|
||||
Negative numbers are treated in the same way as slice indices.
|
||||
|
||||
Raises IndexError if pos < -len(self) or pos >= len(self).
|
||||
|
||||
"""
|
||||
if pos is None:
|
||||
self._invert_all()
|
||||
return
|
||||
if not isinstance(pos, abc.Iterable):
|
||||
pos = (pos,)
|
||||
length = len(self)
|
||||
|
||||
for p in pos:
|
||||
if p < 0:
|
||||
p += length
|
||||
if not 0 <= p < length:
|
||||
raise IndexError(f"Bit position {p} out of range.")
|
||||
self._invert(p)
|
||||
|
||||
def ror(self, bits: int, start: Optional[int] = None, end: Optional[int] = None) -> None:
|
||||
"""Rotate bits to the right in-place.
|
||||
|
||||
bits -- The number of bits to rotate by.
|
||||
start -- Start of slice to rotate. Defaults to 0.
|
||||
end -- End of slice to rotate. Defaults to len(self).
|
||||
|
||||
Raises ValueError if bits < 0.
|
||||
|
||||
"""
|
||||
if not len(self):
|
||||
raise Error("Cannot rotate an empty bitstring.")
|
||||
if bits < 0:
|
||||
raise ValueError("Cannot rotate by negative amount.")
|
||||
self._ror(bits, start, end)
|
||||
|
||||
def _ror_msb0(self, bits: int, start: Optional[int] = None, end: Optional[int] = None) -> None:
|
||||
start, end = self._validate_slice(start, end) # the _slice deals with msb0/lsb0
|
||||
bits %= (end - start)
|
||||
if not bits:
|
||||
return
|
||||
rhs = self._slice(end - bits, end)
|
||||
self._delete(bits, end - bits)
|
||||
self._insert(rhs, start)
|
||||
|
||||
def rol(self, bits: int, start: Optional[int] = None, end: Optional[int] = None) -> None:
|
||||
"""Rotate bits to the left in-place.
|
||||
|
||||
bits -- The number of bits to rotate by.
|
||||
start -- Start of slice to rotate. Defaults to 0.
|
||||
end -- End of slice to rotate. Defaults to len(self).
|
||||
|
||||
Raises ValueError if bits < 0.
|
||||
|
||||
"""
|
||||
if not len(self):
|
||||
raise Error("Cannot rotate an empty bitstring.")
|
||||
if bits < 0:
|
||||
raise ValueError("Cannot rotate by negative amount.")
|
||||
self._rol(bits, start, end)
|
||||
|
||||
def _rol_msb0(self, bits: int, start: Optional[int] = None, end: Optional[int] = None):
|
||||
start, end = self._validate_slice(start, end)
|
||||
bits %= (end - start)
|
||||
if bits == 0:
|
||||
return
|
||||
lhs = self._slice(start, start + bits)
|
||||
self._delete(bits, start)
|
||||
self._insert(lhs, end - bits)
|
||||
|
||||
def byteswap(self, fmt: Optional[Union[int, Iterable[int], str]] = None, start: Optional[int] = None,
|
||||
end: Optional[int] = None, repeat: bool = True) -> int:
|
||||
"""Change the endianness in-place. Return number of repeats of fmt done.
|
||||
|
||||
fmt -- A compact structure string, an integer number of bytes or
|
||||
an iterable of integers. Defaults to 0, which byte reverses the
|
||||
whole bitstring.
|
||||
start -- Start bit position, defaults to 0.
|
||||
end -- End bit position, defaults to len(self).
|
||||
repeat -- If True (the default) the byte swapping pattern is repeated
|
||||
as much as possible.
|
||||
|
||||
"""
|
||||
start_v, end_v = self._validate_slice(start, end)
|
||||
if fmt is None or fmt == 0:
|
||||
# reverse all of the whole bytes.
|
||||
bytesizes = [(end_v - start_v) // 8]
|
||||
elif isinstance(fmt, numbers.Integral):
|
||||
if fmt < 0:
|
||||
raise ValueError(f"Improper byte length {fmt}.")
|
||||
bytesizes = [fmt]
|
||||
elif isinstance(fmt, str):
|
||||
if not (m := utils.BYTESWAP_STRUCT_PACK_RE.match(fmt)):
|
||||
raise ValueError(f"Cannot parse format string {fmt}.")
|
||||
# Split the format string into a list of 'q', '4h' etc.
|
||||
formatlist = re.findall(utils.STRUCT_SPLIT_RE, m.group('fmt'))
|
||||
# Now deal with multiplicative factors, 4h -> hhhh etc.
|
||||
bytesizes = []
|
||||
for f in formatlist:
|
||||
if len(f) == 1:
|
||||
bytesizes.append(utils.PACK_CODE_SIZE[f])
|
||||
else:
|
||||
bytesizes.extend([utils.PACK_CODE_SIZE[f[-1]]] * int(f[:-1]))
|
||||
elif isinstance(fmt, abc.Iterable):
|
||||
bytesizes = fmt
|
||||
for bytesize in bytesizes:
|
||||
if not isinstance(bytesize, numbers.Integral) or bytesize < 0:
|
||||
raise ValueError(f"Improper byte length {bytesize}.")
|
||||
else:
|
||||
raise TypeError("Format must be an integer, string or iterable.")
|
||||
|
||||
repeats = 0
|
||||
totalbitsize: int = 8 * sum(bytesizes)
|
||||
if not totalbitsize:
|
||||
return 0
|
||||
if repeat:
|
||||
# Try to repeat up to the end of the bitstring.
|
||||
finalbit = end_v
|
||||
else:
|
||||
# Just try one (set of) byteswap(s).
|
||||
finalbit = start_v + totalbitsize
|
||||
for patternend in range(start_v + totalbitsize, finalbit + 1, totalbitsize):
|
||||
bytestart = patternend - totalbitsize
|
||||
for bytesize in bytesizes:
|
||||
byteend = bytestart + bytesize * 8
|
||||
self._reversebytes(bytestart, byteend)
|
||||
bytestart += bytesize * 8
|
||||
repeats += 1
|
||||
return repeats
|
||||
|
||||
def clear(self) -> None:
|
||||
"""Remove all bits, reset to zero length."""
|
||||
self._clear()
|
||||
1791
code/.venv/lib/python3.12/site-packages/bitstring/bits.py
Normal file
1791
code/.venv/lib/python3.12/site-packages/bitstring/bits.py
Normal file
File diff suppressed because it is too large
Load Diff
272
code/.venv/lib/python3.12/site-packages/bitstring/bitstore.py
Normal file
272
code/.venv/lib/python3.12/site-packages/bitstring/bitstore.py
Normal file
@@ -0,0 +1,272 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import bitarray
|
||||
from bitstring.exceptions import CreationError
|
||||
from typing import Union, Iterable, Optional, overload, Iterator, Any
|
||||
|
||||
|
||||
def offset_slice_indices_lsb0(key: slice, length: int) -> slice:
|
||||
# First convert slice to all integers
|
||||
# Length already should take account of the offset
|
||||
start, stop, step = key.indices(length)
|
||||
new_start = length - stop
|
||||
new_stop = length - start
|
||||
# For negative step we sometimes get a negative stop, which can't be used correctly in a new slice
|
||||
return slice(new_start, None if new_stop < 0 else new_stop, step)
|
||||
|
||||
|
||||
def offset_start_stop_lsb0(start: Optional[int], stop: Optional[int], length: int) -> tuple[int, int]:
|
||||
# First convert slice to all integers
|
||||
# Length already should take account of the offset
|
||||
start, stop, _ = slice(start, stop, None).indices(length)
|
||||
new_start = length - stop
|
||||
new_stop = length - start
|
||||
return new_start, new_stop
|
||||
|
||||
|
||||
class BitStore:
|
||||
"""A light wrapper around bitarray that does the LSB0 stuff"""
|
||||
|
||||
__slots__ = ('_bitarray', 'modified_length', 'immutable')
|
||||
|
||||
def __init__(self, initializer: Union[int, bitarray.bitarray, str, None] = None,
|
||||
immutable: bool = False) -> None:
|
||||
self._bitarray = bitarray.bitarray(initializer)
|
||||
self.immutable = immutable
|
||||
self.modified_length = None
|
||||
|
||||
@classmethod
|
||||
def frombytes(cls, b: Union[bytes, bytearray, memoryview], /) -> BitStore:
|
||||
x = super().__new__(cls)
|
||||
x._bitarray = bitarray.bitarray()
|
||||
x._bitarray.frombytes(b)
|
||||
x.immutable = False
|
||||
x.modified_length = None
|
||||
return x
|
||||
|
||||
@classmethod
|
||||
def frombuffer(cls, buffer, /, length: Optional[int] = None) -> BitStore:
|
||||
x = super().__new__(cls)
|
||||
x._bitarray = bitarray.bitarray(buffer=buffer)
|
||||
x.immutable = True
|
||||
x.modified_length = length
|
||||
# Here 'modified' means it shouldn't be changed further, so setting, deleting etc. are disallowed.
|
||||
if x.modified_length is not None:
|
||||
if x.modified_length < 0:
|
||||
raise CreationError("Can't create bitstring with a negative length.")
|
||||
if x.modified_length > len(x._bitarray):
|
||||
raise CreationError(
|
||||
f"Can't create bitstring with a length of {x.modified_length} from {len(x._bitarray)} bits of data.")
|
||||
return x
|
||||
|
||||
def setall(self, value: int, /) -> None:
|
||||
self._bitarray.setall(value)
|
||||
|
||||
def tobytes(self) -> bytes:
|
||||
if self.modified_length is not None:
|
||||
return self._bitarray[:self.modified_length].tobytes()
|
||||
return self._bitarray.tobytes()
|
||||
|
||||
def slice_to_uint(self, start: Optional[int] = None, end: Optional[int] = None) -> int:
|
||||
return bitarray.util.ba2int(self.getslice(start, end)._bitarray, signed=False)
|
||||
|
||||
def slice_to_int(self, start: Optional[int] = None, end: Optional[int] = None) -> int:
|
||||
return bitarray.util.ba2int(self.getslice(start, end)._bitarray, signed=True)
|
||||
|
||||
def slice_to_hex(self, start: Optional[int] = None, end: Optional[int] = None) -> str:
|
||||
return bitarray.util.ba2hex(self.getslice(start, end)._bitarray)
|
||||
|
||||
def slice_to_bin(self, start: Optional[int] = None, end: Optional[int] = None) -> str:
|
||||
return self.getslice(start, end)._bitarray.to01()
|
||||
|
||||
def slice_to_oct(self, start: Optional[int] = None, end: Optional[int] = None) -> str:
|
||||
return bitarray.util.ba2base(8, self.getslice(start, end)._bitarray)
|
||||
|
||||
def __iadd__(self, other: BitStore, /) -> BitStore:
|
||||
self._bitarray += other._bitarray
|
||||
return self
|
||||
|
||||
def __add__(self, other: BitStore, /) -> BitStore:
|
||||
bs = self._copy()
|
||||
bs += other
|
||||
return bs
|
||||
|
||||
def __eq__(self, other: Any, /) -> bool:
|
||||
return self._bitarray == other._bitarray
|
||||
|
||||
def __and__(self, other: BitStore, /) -> BitStore:
|
||||
return BitStore(self._bitarray & other._bitarray)
|
||||
|
||||
def __or__(self, other: BitStore, /) -> BitStore:
|
||||
return BitStore(self._bitarray | other._bitarray)
|
||||
|
||||
def __xor__(self, other: BitStore, /) -> BitStore:
|
||||
return BitStore(self._bitarray ^ other._bitarray)
|
||||
|
||||
def __iand__(self, other: BitStore, /) -> BitStore:
|
||||
self._bitarray &= other._bitarray
|
||||
return self
|
||||
|
||||
def __ior__(self, other: BitStore, /) -> BitStore:
|
||||
self._bitarray |= other._bitarray
|
||||
return self
|
||||
|
||||
def __ixor__(self, other: BitStore, /) -> BitStore:
|
||||
self._bitarray ^= other._bitarray
|
||||
return self
|
||||
|
||||
def find(self, bs: BitStore, start: int, end: int, bytealigned: bool = False) -> int:
|
||||
if not bytealigned:
|
||||
return self._bitarray.find(bs._bitarray, start, end)
|
||||
try:
|
||||
return next(self.findall_msb0(bs, start, end, bytealigned))
|
||||
except StopIteration:
|
||||
return -1
|
||||
|
||||
def rfind(self, bs: BitStore, start: int, end: int, bytealigned: bool = False):
|
||||
if not bytealigned:
|
||||
return self._bitarray.find(bs._bitarray, start, end, right=True)
|
||||
try:
|
||||
return next(self.rfindall_msb0(bs, start, end, bytealigned))
|
||||
except StopIteration:
|
||||
return -1
|
||||
|
||||
def findall_msb0(self, bs: BitStore, start: int, end: int, bytealigned: bool = False) -> Iterator[int]:
|
||||
if bytealigned is True and len(bs) % 8 == 0:
|
||||
# Special case, looking for whole bytes on whole byte boundaries
|
||||
bytes_ = bs.tobytes()
|
||||
# Round up start byte to next byte, and round end byte down.
|
||||
# We're only looking for whole bytes, so can ignore bits at either end.
|
||||
start_byte = (start + 7) // 8
|
||||
end_byte = end // 8
|
||||
b = self._bitarray[start_byte * 8: end_byte * 8].tobytes()
|
||||
byte_pos = 0
|
||||
bytes_to_search = end_byte - start_byte
|
||||
while byte_pos < bytes_to_search:
|
||||
byte_pos = b.find(bytes_, byte_pos)
|
||||
if byte_pos == -1:
|
||||
break
|
||||
yield (byte_pos + start_byte) * 8
|
||||
byte_pos = byte_pos + 1
|
||||
return
|
||||
# General case
|
||||
i = self._bitarray.itersearch(bs._bitarray, start, end)
|
||||
if not bytealigned:
|
||||
for p in i:
|
||||
yield p
|
||||
else:
|
||||
for p in i:
|
||||
if (p % 8) == 0:
|
||||
yield p
|
||||
|
||||
def rfindall_msb0(self, bs: BitStore, start: int, end: int, bytealigned: bool = False) -> Iterator[int]:
|
||||
i = self._bitarray.itersearch(bs._bitarray, start, end, right=True)
|
||||
if not bytealigned:
|
||||
for p in i:
|
||||
yield p
|
||||
else:
|
||||
for p in i:
|
||||
if (p % 8) == 0:
|
||||
yield p
|
||||
|
||||
def count(self, value, /) -> int:
|
||||
return self._bitarray.count(value)
|
||||
|
||||
def clear(self) -> None:
|
||||
self._bitarray.clear()
|
||||
|
||||
def reverse(self) -> None:
|
||||
self._bitarray.reverse()
|
||||
|
||||
def __iter__(self) -> Iterable[bool]:
|
||||
for i in range(len(self)):
|
||||
yield self.getindex(i)
|
||||
|
||||
def _copy(self) -> BitStore:
|
||||
"""Always creates a copy, even if instance is immutable."""
|
||||
return BitStore(self._bitarray)
|
||||
|
||||
def copy(self) -> BitStore:
|
||||
return self if self.immutable else self._copy()
|
||||
|
||||
def __getitem__(self, item: Union[int, slice], /) -> Union[int, BitStore]:
|
||||
# Use getindex or getslice instead
|
||||
raise NotImplementedError
|
||||
|
||||
def getindex_msb0(self, index: int, /) -> bool:
|
||||
return bool(self._bitarray.__getitem__(index))
|
||||
|
||||
def getslice_withstep_msb0(self, key: slice, /) -> BitStore:
|
||||
if self.modified_length is not None:
|
||||
key = slice(*key.indices(self.modified_length))
|
||||
return BitStore(self._bitarray.__getitem__(key))
|
||||
|
||||
def getslice_withstep_lsb0(self, key: slice, /) -> BitStore:
|
||||
key = offset_slice_indices_lsb0(key, len(self))
|
||||
return BitStore(self._bitarray.__getitem__(key))
|
||||
|
||||
def getslice_msb0(self, start: Optional[int], stop: Optional[int], /) -> BitStore:
|
||||
if self.modified_length is not None:
|
||||
key = slice(*slice(start, stop, None).indices(self.modified_length))
|
||||
start = key.start
|
||||
stop = key.stop
|
||||
return BitStore(self._bitarray[start:stop])
|
||||
|
||||
def getslice_lsb0(self, start: Optional[int], stop: Optional[int], /) -> BitStore:
|
||||
start, stop = offset_start_stop_lsb0(start, stop, len(self))
|
||||
return BitStore(self._bitarray[start:stop])
|
||||
|
||||
def getindex_lsb0(self, index: int, /) -> bool:
|
||||
return bool(self._bitarray.__getitem__(-index - 1))
|
||||
|
||||
@overload
|
||||
def setitem_lsb0(self, key: int, value: int, /) -> None:
|
||||
...
|
||||
|
||||
@overload
|
||||
def setitem_lsb0(self, key: slice, value: BitStore, /) -> None:
|
||||
...
|
||||
|
||||
def setitem_lsb0(self, key: Union[int, slice], value: Union[int, BitStore], /) -> None:
|
||||
if isinstance(key, slice):
|
||||
new_slice = offset_slice_indices_lsb0(key, len(self))
|
||||
self._bitarray.__setitem__(new_slice, value._bitarray)
|
||||
else:
|
||||
self._bitarray.__setitem__(-key - 1, value)
|
||||
|
||||
def delitem_lsb0(self, key: Union[int, slice], /) -> None:
|
||||
if isinstance(key, slice):
|
||||
new_slice = offset_slice_indices_lsb0(key, len(self))
|
||||
self._bitarray.__delitem__(new_slice)
|
||||
else:
|
||||
self._bitarray.__delitem__(-key - 1)
|
||||
|
||||
def invert_msb0(self, index: Optional[int] = None, /) -> None:
|
||||
if index is not None:
|
||||
self._bitarray.invert(index)
|
||||
else:
|
||||
self._bitarray.invert()
|
||||
|
||||
def invert_lsb0(self, index: Optional[int] = None, /) -> None:
|
||||
if index is not None:
|
||||
self._bitarray.invert(-index - 1)
|
||||
else:
|
||||
self._bitarray.invert()
|
||||
|
||||
def any_set(self) -> bool:
|
||||
return self._bitarray.any()
|
||||
|
||||
def all_set(self) -> bool:
|
||||
return self._bitarray.all()
|
||||
|
||||
def __len__(self) -> int:
|
||||
return self.modified_length if self.modified_length is not None else len(self._bitarray)
|
||||
|
||||
def setitem_msb0(self, key, value, /):
|
||||
if isinstance(value, BitStore):
|
||||
self._bitarray.__setitem__(key, value._bitarray)
|
||||
else:
|
||||
self._bitarray.__setitem__(key, value)
|
||||
|
||||
def delitem_msb0(self, key, /):
|
||||
self._bitarray.__delitem__(key)
|
||||
@@ -0,0 +1,270 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import struct
|
||||
import math
|
||||
import functools
|
||||
from typing import Union, Optional, Dict, Callable
|
||||
import bitarray
|
||||
from bitstring.bitstore import BitStore
|
||||
import bitstring
|
||||
from bitstring.fp8 import p4binary_fmt, p3binary_fmt
|
||||
from bitstring.mxfp import (e3m2mxfp_fmt, e2m3mxfp_fmt, e2m1mxfp_fmt, e4m3mxfp_saturate_fmt,
|
||||
e5m2mxfp_saturate_fmt, e4m3mxfp_overflow_fmt, e5m2mxfp_overflow_fmt)
|
||||
|
||||
# The size of various caches used to improve performance
|
||||
CACHE_SIZE = 256
|
||||
|
||||
|
||||
def tidy_input_string(s: str) -> str:
|
||||
"""Return string made lowercase and with all whitespace and underscores removed."""
|
||||
try:
|
||||
t = s.split()
|
||||
except (AttributeError, TypeError):
|
||||
raise ValueError(f"Expected str object but received a {type(s)} with value {s}.")
|
||||
return ''.join(t).lower().replace('_', '')
|
||||
|
||||
|
||||
@functools.lru_cache(CACHE_SIZE)
|
||||
def str_to_bitstore(s: str) -> BitStore:
|
||||
_, tokens = bitstring.utils.tokenparser(s)
|
||||
bs = BitStore()
|
||||
for token in tokens:
|
||||
bs += bitstore_from_token(*token)
|
||||
bs.immutable = True
|
||||
return bs
|
||||
|
||||
|
||||
def bin2bitstore(binstring: str) -> BitStore:
|
||||
binstring = tidy_input_string(binstring)
|
||||
binstring = binstring.replace('0b', '')
|
||||
try:
|
||||
return BitStore(binstring)
|
||||
except ValueError:
|
||||
raise bitstring.CreationError(f"Invalid character in bin initialiser {binstring}.")
|
||||
|
||||
|
||||
def bin2bitstore_unsafe(binstring: str) -> BitStore:
|
||||
return BitStore(binstring)
|
||||
|
||||
|
||||
def hex2bitstore(hexstring: str) -> BitStore:
|
||||
hexstring = tidy_input_string(hexstring)
|
||||
hexstring = hexstring.replace('0x', '')
|
||||
try:
|
||||
ba = bitarray.util.hex2ba(hexstring)
|
||||
except ValueError:
|
||||
raise bitstring.CreationError("Invalid symbol in hex initialiser.")
|
||||
return BitStore(ba)
|
||||
|
||||
|
||||
def oct2bitstore(octstring: str) -> BitStore:
|
||||
octstring = tidy_input_string(octstring)
|
||||
octstring = octstring.replace('0o', '')
|
||||
try:
|
||||
ba = bitarray.util.base2ba(8, octstring)
|
||||
except ValueError:
|
||||
raise bitstring.CreationError("Invalid symbol in oct initialiser.")
|
||||
return BitStore(ba)
|
||||
|
||||
|
||||
def ue2bitstore(i: Union[str, int]) -> BitStore:
|
||||
i = int(i)
|
||||
if i < 0:
|
||||
raise bitstring.CreationError("Cannot use negative initialiser for unsigned exponential-Golomb.")
|
||||
if i == 0:
|
||||
return BitStore('1')
|
||||
tmp = i + 1
|
||||
leadingzeros = -1
|
||||
while tmp > 0:
|
||||
tmp >>= 1
|
||||
leadingzeros += 1
|
||||
remainingpart = i + 1 - (1 << leadingzeros)
|
||||
return BitStore('0' * leadingzeros + '1') + int2bitstore(remainingpart, leadingzeros, False)
|
||||
|
||||
|
||||
def se2bitstore(i: Union[str, int]) -> BitStore:
|
||||
i = int(i)
|
||||
if i > 0:
|
||||
u = (i * 2) - 1
|
||||
else:
|
||||
u = -2 * i
|
||||
return ue2bitstore(u)
|
||||
|
||||
|
||||
def uie2bitstore(i: Union[str, int]) -> BitStore:
|
||||
i = int(i)
|
||||
if i < 0:
|
||||
raise bitstring.CreationError("Cannot use negative initialiser for unsigned interleaved exponential-Golomb.")
|
||||
return BitStore('1' if i == 0 else '0' + '0'.join(bin(i + 1)[3:]) + '1')
|
||||
|
||||
|
||||
def sie2bitstore(i: Union[str, int]) -> BitStore:
|
||||
i = int(i)
|
||||
if i == 0:
|
||||
return BitStore('1')
|
||||
else:
|
||||
return uie2bitstore(abs(i)) + (BitStore('1') if i < 0 else BitStore('0'))
|
||||
|
||||
|
||||
def bfloat2bitstore(f: Union[str, float], big_endian: bool) -> BitStore:
|
||||
f = float(f)
|
||||
fmt = '>f' if big_endian else '<f'
|
||||
try:
|
||||
b = struct.pack(fmt, f)
|
||||
except OverflowError:
|
||||
# For consistency we overflow to 'inf'.
|
||||
b = struct.pack(fmt, float('inf') if f > 0 else float('-inf'))
|
||||
return BitStore.frombytes(b[0:2]) if big_endian else BitStore.frombytes(b[2:4])
|
||||
|
||||
|
||||
def p4binary2bitstore(f: Union[str, float]) -> BitStore:
|
||||
f = float(f)
|
||||
u = p4binary_fmt.float_to_int8(f)
|
||||
return int2bitstore(u, 8, False)
|
||||
|
||||
|
||||
def p3binary2bitstore(f: Union[str, float]) -> BitStore:
|
||||
f = float(f)
|
||||
u = p3binary_fmt.float_to_int8(f)
|
||||
return int2bitstore(u, 8, False)
|
||||
|
||||
|
||||
def e4m3mxfp2bitstore(f: Union[str, float]) -> BitStore:
|
||||
f = float(f)
|
||||
if bitstring.options.mxfp_overflow == 'saturate':
|
||||
u = e4m3mxfp_saturate_fmt.float_to_int(f)
|
||||
else:
|
||||
u = e4m3mxfp_overflow_fmt.float_to_int(f)
|
||||
return int2bitstore(u, 8, False)
|
||||
|
||||
|
||||
def e5m2mxfp2bitstore(f: Union[str, float]) -> BitStore:
|
||||
f = float(f)
|
||||
if bitstring.options.mxfp_overflow == 'saturate':
|
||||
u = e5m2mxfp_saturate_fmt.float_to_int(f)
|
||||
else:
|
||||
u = e5m2mxfp_overflow_fmt.float_to_int(f)
|
||||
return int2bitstore(u, 8, False)
|
||||
|
||||
|
||||
def e3m2mxfp2bitstore(f: Union[str, float]) -> BitStore:
|
||||
f = float(f)
|
||||
if math.isnan(f):
|
||||
raise ValueError("Cannot convert float('nan') to e3m2mxfp format as it has no representation for it.")
|
||||
u = e3m2mxfp_fmt.float_to_int(f)
|
||||
return int2bitstore(u, 6, False)
|
||||
|
||||
|
||||
def e2m3mxfp2bitstore(f: Union[str, float]) -> BitStore:
|
||||
f = float(f)
|
||||
if math.isnan(f):
|
||||
raise ValueError("Cannot convert float('nan') to e2m3mxfp format as it has no representation for it.")
|
||||
u = e2m3mxfp_fmt.float_to_int(f)
|
||||
return int2bitstore(u, 6, False)
|
||||
|
||||
|
||||
def e2m1mxfp2bitstore(f: Union[str, float]) -> BitStore:
|
||||
f = float(f)
|
||||
if math.isnan(f):
|
||||
raise ValueError("Cannot convert float('nan') to e2m1mxfp format as it has no representation for it.")
|
||||
u = e2m1mxfp_fmt.float_to_int(f)
|
||||
return int2bitstore(u, 4, False)
|
||||
|
||||
|
||||
e8m0mxfp_allowed_values = [float(2 ** x) for x in range(-127, 128)]
|
||||
|
||||
|
||||
def e8m0mxfp2bitstore(f: Union[str, float]) -> BitStore:
|
||||
f = float(f)
|
||||
if math.isnan(f):
|
||||
return BitStore('11111111')
|
||||
try:
|
||||
i = e8m0mxfp_allowed_values.index(f)
|
||||
except ValueError:
|
||||
raise ValueError(f"{f} is not a valid e8m0mxfp value. It must be exactly 2 ** i, for -127 <= i <= 127 or float('nan') as no rounding will be done.")
|
||||
return int2bitstore(i, 8, False)
|
||||
|
||||
|
||||
def mxint2bitstore(f: Union[str, float]) -> BitStore:
|
||||
f = float(f)
|
||||
if math.isnan(f):
|
||||
raise ValueError("Cannot convert float('nan') to mxint format as it has no representation for it.")
|
||||
f *= 2 ** 6 # Remove the implicit scaling factor
|
||||
if f > 127: # 1 + 63/64
|
||||
return BitStore('01111111')
|
||||
if f <= -128: # -2
|
||||
return BitStore('10000000')
|
||||
# Want to round to nearest, so move by 0.5 away from zero and round down by converting to int
|
||||
if f >= 0.0:
|
||||
f += 0.5
|
||||
i = int(f)
|
||||
# For ties-round-to-even
|
||||
if f - i == 0.0 and i % 2:
|
||||
i -= 1
|
||||
else:
|
||||
f -= 0.5
|
||||
i = int(f)
|
||||
if f - i == 0.0 and i % 2:
|
||||
i += 1
|
||||
return int2bitstore(i, 8, True)
|
||||
|
||||
|
||||
def int2bitstore(i: int, length: int, signed: bool) -> BitStore:
|
||||
i = int(i)
|
||||
try:
|
||||
x = BitStore(bitarray.util.int2ba(i, length=length, endian='big', signed=signed))
|
||||
except OverflowError as e:
|
||||
if signed:
|
||||
if i >= (1 << (length - 1)) or i < -(1 << (length - 1)):
|
||||
raise bitstring.CreationError(f"{i} is too large a signed integer for a bitstring of length {length}. "
|
||||
f"The allowed range is [{-(1 << (length - 1))}, {(1 << (length - 1)) - 1}].")
|
||||
else:
|
||||
if i >= (1 << length):
|
||||
raise bitstring.CreationError(f"{i} is too large an unsigned integer for a bitstring of length {length}. "
|
||||
f"The allowed range is [0, {(1 << length) - 1}].")
|
||||
if i < 0:
|
||||
raise bitstring.CreationError("uint cannot be initialised with a negative number.")
|
||||
raise e
|
||||
return x
|
||||
|
||||
|
||||
def intle2bitstore(i: int, length: int, signed: bool) -> BitStore:
|
||||
x = int2bitstore(i, length, signed).tobytes()
|
||||
return BitStore.frombytes(x[::-1])
|
||||
|
||||
|
||||
def float2bitstore(f: Union[str, float], length: int, big_endian: bool) -> BitStore:
|
||||
f = float(f)
|
||||
fmt = {16: '>e', 32: '>f', 64: '>d'}[length] if big_endian else {16: '<e', 32: '<f', 64: '<d'}[length]
|
||||
try:
|
||||
b = struct.pack(fmt, f)
|
||||
except OverflowError:
|
||||
# If float64 doesn't fit it automatically goes to 'inf'. This reproduces that behaviour for other types.
|
||||
b = struct.pack(fmt, float('inf') if f > 0 else float('-inf'))
|
||||
return BitStore.frombytes(b)
|
||||
|
||||
|
||||
literal_bit_funcs: Dict[str, Callable[..., BitStore]] = {
|
||||
'0x': hex2bitstore,
|
||||
'0X': hex2bitstore,
|
||||
'0b': bin2bitstore,
|
||||
'0B': bin2bitstore,
|
||||
'0o': oct2bitstore,
|
||||
'0O': oct2bitstore,
|
||||
}
|
||||
|
||||
|
||||
def bitstore_from_token(name: str, token_length: Optional[int], value: Optional[str]) -> BitStore:
|
||||
if name in literal_bit_funcs:
|
||||
return literal_bit_funcs[name](value)
|
||||
try:
|
||||
d = bitstring.dtypes.Dtype(name, token_length)
|
||||
except ValueError as e:
|
||||
raise bitstring.CreationError(f"Can't parse token: {e}")
|
||||
if value is None and name != 'pad':
|
||||
raise ValueError(f"Token {name} requires a value.")
|
||||
bs = d.build(value)._bitstore
|
||||
if token_length is not None and len(bs) != d.bitlength:
|
||||
raise bitstring.CreationError(f"Token with length {token_length} packed with value of length {len(bs)} "
|
||||
f"({name}:{token_length}={value}).")
|
||||
return bs
|
||||
714
code/.venv/lib/python3.12/site-packages/bitstring/bitstream.py
Normal file
714
code/.venv/lib/python3.12/site-packages/bitstring/bitstream.py
Normal file
@@ -0,0 +1,714 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import bitstring
|
||||
from bitstring.bits import Bits, BitsType
|
||||
from bitstring.dtypes import Dtype
|
||||
from typing import Union, List, Any, Optional, overload, TypeVar, Tuple
|
||||
import copy
|
||||
import numbers
|
||||
|
||||
TConstBitStream = TypeVar("TConstBitStream", bound='ConstBitStream')
|
||||
|
||||
|
||||
class ConstBitStream(Bits):
|
||||
"""A container or stream holding an immutable sequence of bits.
|
||||
|
||||
For a mutable container use the BitStream class instead.
|
||||
|
||||
Methods inherited from Bits:
|
||||
|
||||
all() -- Check if all specified bits are set to 1 or 0.
|
||||
any() -- Check if any of specified bits are set to 1 or 0.
|
||||
copy() -- Return a copy of the bitstring.
|
||||
count() -- Count the number of bits set to 1 or 0.
|
||||
cut() -- Create generator of constant sized chunks.
|
||||
endswith() -- Return whether the bitstring ends with a sub-string.
|
||||
find() -- Find a sub-bitstring in the current bitstring.
|
||||
findall() -- Find all occurrences of a sub-bitstring in the current bitstring.
|
||||
fromstring() -- Create a bitstring from a formatted string.
|
||||
join() -- Join bitstrings together using current bitstring.
|
||||
pp() -- Pretty print the bitstring.
|
||||
rfind() -- Seek backwards to find a sub-bitstring.
|
||||
split() -- Create generator of chunks split by a delimiter.
|
||||
startswith() -- Return whether the bitstring starts with a sub-bitstring.
|
||||
tobitarray() -- Return bitstring as a bitarray from the bitarray package.
|
||||
tobytes() -- Return bitstring as bytes, padding if needed.
|
||||
tofile() -- Write bitstring to file, padding if needed.
|
||||
unpack() -- Interpret bits using format string.
|
||||
|
||||
Other methods:
|
||||
|
||||
bytealign() -- Align to next byte boundary.
|
||||
peek() -- Peek at and interpret next bits as a single item.
|
||||
peeklist() -- Peek at and interpret next bits as a list of items.
|
||||
read() -- Read and interpret next bits as a single item.
|
||||
readlist() -- Read and interpret next bits as a list of items.
|
||||
readto() -- Read up to and including next occurrence of a bitstring.
|
||||
|
||||
Special methods:
|
||||
|
||||
Also available are the operators [], ==, !=, +, *, ~, <<, >>, &, |, ^.
|
||||
|
||||
Properties:
|
||||
|
||||
[GENERATED_PROPERTY_DESCRIPTIONS]
|
||||
|
||||
len -- Length of the bitstring in bits.
|
||||
pos -- The current bit position in the bitstring.
|
||||
"""
|
||||
|
||||
__slots__ = ('_pos',)
|
||||
|
||||
def __init__(self, auto: Optional[Union[BitsType, int]] = None, /, length: Optional[int] = None,
|
||||
offset: Optional[int] = None, pos: int = 0, **kwargs) -> None:
|
||||
"""Either specify an 'auto' initialiser:
|
||||
A string of comma separated tokens, an integer, a file object,
|
||||
a bytearray, a boolean iterable or another bitstring.
|
||||
|
||||
Or initialise via **kwargs with one (and only one) of:
|
||||
bin -- binary string representation, e.g. '0b001010'.
|
||||
hex -- hexadecimal string representation, e.g. '0x2ef'
|
||||
oct -- octal string representation, e.g. '0o777'.
|
||||
bytes -- raw data as a bytes object, for example read from a binary file.
|
||||
int -- a signed integer.
|
||||
uint -- an unsigned integer.
|
||||
float / floatbe -- a big-endian floating point number.
|
||||
bool -- a boolean (True or False).
|
||||
se -- a signed exponential-Golomb code.
|
||||
ue -- an unsigned exponential-Golomb code.
|
||||
sie -- a signed interleaved exponential-Golomb code.
|
||||
uie -- an unsigned interleaved exponential-Golomb code.
|
||||
floatle -- a little-endian floating point number.
|
||||
floatne -- a native-endian floating point number.
|
||||
bfloat / bfloatbe - a big-endian bfloat format 16-bit floating point number.
|
||||
bfloatle -- a little-endian bfloat format 16-bit floating point number.
|
||||
bfloatne -- a native-endian bfloat format 16-bit floating point number.
|
||||
intbe -- a signed big-endian whole byte integer.
|
||||
intle -- a signed little-endian whole byte integer.
|
||||
intne -- a signed native-endian whole byte integer.
|
||||
uintbe -- an unsigned big-endian whole byte integer.
|
||||
uintle -- an unsigned little-endian whole byte integer.
|
||||
uintne -- an unsigned native-endian whole byte integer.
|
||||
filename -- the path of a file which will be opened in binary read-only mode.
|
||||
|
||||
Other keyword arguments:
|
||||
length -- length of the bitstring in bits, if needed and appropriate.
|
||||
It must be supplied for all integer and float initialisers.
|
||||
offset -- bit offset to the data. These offset bits are
|
||||
ignored and this is mainly intended for use when
|
||||
initialising using 'bytes' or 'filename'.
|
||||
pos -- Initial bit position, defaults to 0.
|
||||
|
||||
"""
|
||||
if pos < 0:
|
||||
pos += len(self._bitstore)
|
||||
if pos < 0 or pos > len(self._bitstore):
|
||||
raise bitstring.CreationError(f"Cannot set pos to {pos} when length is {len(self._bitstore)}.")
|
||||
self._pos = pos
|
||||
self._bitstore.immutable = True
|
||||
|
||||
def _setbytepos(self, bytepos: int) -> None:
|
||||
"""Move to absolute byte-aligned position in stream."""
|
||||
self._setbitpos(bytepos * 8)
|
||||
|
||||
def _getbytepos(self) -> int:
|
||||
"""Return the current position in the stream in bytes. Must be byte aligned."""
|
||||
if self._pos % 8:
|
||||
raise bitstring.ByteAlignError("Not byte aligned when using bytepos property.")
|
||||
return self._pos // 8
|
||||
|
||||
def _setbitpos(self, pos: int) -> None:
|
||||
"""Move to absolute position bit in bitstream."""
|
||||
if pos < 0:
|
||||
raise ValueError("Bit position cannot be negative.")
|
||||
if pos > len(self):
|
||||
raise ValueError("Cannot seek past the end of the data.")
|
||||
self._pos = pos
|
||||
|
||||
def _getbitpos(self) -> int:
|
||||
"""Return the current position in the stream in bits."""
|
||||
return self._pos
|
||||
|
||||
def _clear(self) -> None:
|
||||
Bits._clear(self)
|
||||
self._pos = 0
|
||||
|
||||
def __copy__(self: TConstBitStream) -> TConstBitStream:
|
||||
"""Return a new copy of the ConstBitStream for the copy module."""
|
||||
# Note that if you want a new copy (different ID), use _copy instead.
|
||||
# The copy can use the same datastore as it's immutable.
|
||||
s = self.__class__()
|
||||
s._bitstore = self._bitstore
|
||||
# Reset the bit position, don't copy it.
|
||||
s._pos = 0
|
||||
return s
|
||||
|
||||
def __and__(self: TConstBitStream, bs: BitsType, /) -> TConstBitStream:
|
||||
"""Bit-wise 'and' between two bitstrings. Returns new bitstring.
|
||||
|
||||
bs -- The bitstring to '&' with.
|
||||
|
||||
Raises ValueError if the two bitstrings have differing lengths.
|
||||
|
||||
"""
|
||||
s = Bits.__and__(self, bs)
|
||||
s._pos = 0
|
||||
return s
|
||||
|
||||
def __or__(self: TConstBitStream, bs: BitsType, /) -> TConstBitStream:
|
||||
"""Bit-wise 'or' between two bitstrings. Returns new bitstring.
|
||||
|
||||
bs -- The bitstring to '|' with.
|
||||
|
||||
Raises ValueError if the two bitstrings have differing lengths.
|
||||
|
||||
"""
|
||||
s = Bits.__or__(self, bs)
|
||||
s._pos = 0
|
||||
return s
|
||||
|
||||
def __xor__(self: TConstBitStream, bs: BitsType, /) -> TConstBitStream:
|
||||
"""Bit-wise 'xor' between two bitstrings. Returns new bitstring.
|
||||
|
||||
bs -- The bitstring to '^' with.
|
||||
|
||||
Raises ValueError if the two bitstrings have differing lengths.
|
||||
|
||||
"""
|
||||
s = Bits.__xor__(self, bs)
|
||||
s._pos = 0
|
||||
return s
|
||||
|
||||
def __add__(self: TConstBitStream, bs: BitsType, /) -> TConstBitStream:
|
||||
"""Concatenate bitstrings and return new bitstring.
|
||||
|
||||
bs -- the bitstring to append.
|
||||
|
||||
"""
|
||||
s = Bits.__add__(self, bs)
|
||||
s._pos = 0
|
||||
return s
|
||||
|
||||
def append(self, bs: BitsType, /) -> None:
|
||||
"""Append a bitstring to the current bitstring.
|
||||
|
||||
bs -- The bitstring to append.
|
||||
|
||||
The current bit position will be moved to the end of the BitStream.
|
||||
|
||||
"""
|
||||
self._append(bs)
|
||||
self._pos = len(self)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
"""Return representation that could be used to recreate the bitstring.
|
||||
|
||||
If the returned string is too long it will be truncated. See __str__().
|
||||
|
||||
"""
|
||||
return self._repr(self.__class__.__name__, len(self), self._pos)
|
||||
|
||||
def overwrite(self, bs: BitsType, /, pos: Optional[int] = None) -> None:
|
||||
"""Overwrite with bitstring at bit position pos.
|
||||
|
||||
bs -- The bitstring to overwrite with.
|
||||
pos -- The bit position to begin overwriting from.
|
||||
|
||||
The current bit position will be moved to the end of the overwritten section.
|
||||
Raises ValueError if pos < 0 or pos > len(self).
|
||||
|
||||
"""
|
||||
bs = Bits._create_from_bitstype(bs)
|
||||
if len(bs) == 0:
|
||||
return
|
||||
if pos is None:
|
||||
pos = self._pos
|
||||
if pos < 0:
|
||||
pos += len(self)
|
||||
if pos < 0 or pos > len(self):
|
||||
raise ValueError("Overwrite starts outside boundary of bitstring.")
|
||||
self._overwrite(bs, pos)
|
||||
self._pos = pos + len(bs)
|
||||
|
||||
def find(self, bs: BitsType, /, start: Optional[int] = None, end: Optional[int] = None,
|
||||
bytealigned: Optional[bool] = None) -> Union[Tuple[int], Tuple[()]]:
|
||||
"""Find first occurrence of substring bs.
|
||||
|
||||
Returns a single item tuple with the bit position if found, or an
|
||||
empty tuple if not found. The bit position (pos property) will
|
||||
also be set to the start of the substring if it is found.
|
||||
|
||||
bs -- The bitstring to find.
|
||||
start -- The bit position to start the search. Defaults to 0.
|
||||
end -- The bit position one past the last bit to search.
|
||||
Defaults to len(self).
|
||||
bytealigned -- If True the bitstring will only be
|
||||
found on byte boundaries.
|
||||
|
||||
Raises ValueError if bs is empty, if start < 0, if end > len(self) or
|
||||
if end < start.
|
||||
|
||||
>>> BitStream('0xc3e').find('0b1111')
|
||||
(6,)
|
||||
|
||||
"""
|
||||
|
||||
p = super().find(bs, start, end, bytealigned)
|
||||
if p:
|
||||
self._pos = p[0]
|
||||
return p
|
||||
|
||||
def rfind(self, bs: BitsType, /, start: Optional[int] = None, end: Optional[int] = None,
|
||||
bytealigned: Optional[bool] = None) -> Union[Tuple[int], Tuple[()]]:
|
||||
"""Find final occurrence of substring bs.
|
||||
|
||||
Returns a single item tuple with the bit position if found, or an
|
||||
empty tuple if not found. The bit position (pos property) will
|
||||
also be set to the start of the substring if it is found.
|
||||
|
||||
bs -- The bitstring to find.
|
||||
start -- The bit position to end the reverse search. Defaults to 0.
|
||||
end -- The bit position one past the first bit to reverse search.
|
||||
Defaults to len(self).
|
||||
bytealigned -- If True the bitstring will only be found on byte
|
||||
boundaries.
|
||||
|
||||
Raises ValueError if bs is empty, if start < 0, if end > len(self) or
|
||||
if end < start.
|
||||
|
||||
"""
|
||||
p = super().rfind(bs, start, end, bytealigned)
|
||||
if p:
|
||||
self._pos = p[0]
|
||||
return p
|
||||
|
||||
@overload
|
||||
def read(self, fmt: int) -> Bits:
|
||||
...
|
||||
|
||||
@overload
|
||||
def read(self, fmt: str) -> Any:
|
||||
...
|
||||
|
||||
def read(self, fmt: Union[int, str, Dtype]) -> Union[int, float, str, Bits, bool, bytes, None]:
|
||||
"""Interpret next bits according to the format string and return result.
|
||||
|
||||
fmt -- Token string describing how to interpret the next bits.
|
||||
|
||||
Token examples: 'int:12' : 12 bits as a signed integer
|
||||
'uint:8' : 8 bits as an unsigned integer
|
||||
'float:64' : 8 bytes as a big-endian float
|
||||
'intbe:16' : 2 bytes as a big-endian signed integer
|
||||
'uintbe:16' : 2 bytes as a big-endian unsigned integer
|
||||
'intle:32' : 4 bytes as a little-endian signed integer
|
||||
'uintle:32' : 4 bytes as a little-endian unsigned integer
|
||||
'floatle:64': 8 bytes as a little-endian float
|
||||
'intne:24' : 3 bytes as a native-endian signed integer
|
||||
'uintne:24' : 3 bytes as a native-endian unsigned integer
|
||||
'floatne:32': 4 bytes as a native-endian float
|
||||
'hex:80' : 80 bits as a hex string
|
||||
'oct:9' : 9 bits as an octal string
|
||||
'bin:1' : single bit binary string
|
||||
'ue' : next bits as unsigned exp-Golomb code
|
||||
'se' : next bits as signed exp-Golomb code
|
||||
'uie' : next bits as unsigned interleaved exp-Golomb code
|
||||
'sie' : next bits as signed interleaved exp-Golomb code
|
||||
'bits:5' : 5 bits as a bitstring
|
||||
'bytes:10' : 10 bytes as a bytes object
|
||||
'bool' : 1 bit as a bool
|
||||
'pad:3' : 3 bits of padding to ignore - returns None
|
||||
|
||||
fmt may also be an integer, which will be treated like the 'bits' token.
|
||||
|
||||
The position in the bitstring is advanced to after the read items.
|
||||
|
||||
Raises ReadError if not enough bits are available.
|
||||
Raises ValueError if the format is not understood.
|
||||
|
||||
"""
|
||||
p = self._pos
|
||||
if isinstance(fmt, numbers.Integral):
|
||||
if fmt < 0:
|
||||
raise ValueError("Cannot read negative amount.")
|
||||
if fmt > len(self) - self._pos:
|
||||
raise bitstring.ReadError(f"Cannot read {fmt} bits, only {len(self) - self._pos} available.")
|
||||
bs = self._slice(self._pos, self._pos + fmt)
|
||||
self._pos += fmt
|
||||
return bs
|
||||
dtype = bitstring.dtypes.Dtype(fmt)
|
||||
if dtype.bitlength is None and not dtype.variable_length:
|
||||
# No length specified? Try again, but read to end.
|
||||
bitlength = len(self) - self._pos
|
||||
items, remainder = divmod(bitlength, dtype.bits_per_item)
|
||||
if remainder != 0:
|
||||
raise ValueError(
|
||||
f"The '{dtype.name}' type must have a bit length that is a multiple of {dtype.bits_per_item}"
|
||||
f" so cannot be read from the {bitlength} bits that are available.")
|
||||
dtype = bitstring.dtypes.Dtype(fmt, items)
|
||||
if dtype.bitlength is not None:
|
||||
val = dtype.read_fn(self, self._pos)
|
||||
self._pos += dtype.bitlength
|
||||
else:
|
||||
val, self._pos = dtype.read_fn(self, self._pos)
|
||||
|
||||
if self._pos > len(self):
|
||||
self._pos = p
|
||||
raise bitstring.ReadError(f"Reading off end of bitstring with fmt '{fmt}'. Only {len(self) - p} bits available.")
|
||||
return val
|
||||
|
||||
def readlist(self, fmt: Union[str, List[Union[int, str, Dtype]]], **kwargs) \
|
||||
-> List[Union[int, float, str, Bits, bool, bytes, None]]:
|
||||
"""Interpret next bits according to format string(s) and return list.
|
||||
|
||||
fmt -- A single string or list of strings with comma separated tokens
|
||||
describing how to interpret the next bits in the bitstring. Items
|
||||
can also be integers, for reading new bitstring of the given length.
|
||||
kwargs -- A dictionary or keyword-value pairs - the keywords used in the
|
||||
format string will be replaced with their given value.
|
||||
|
||||
The position in the bitstring is advanced to after the read items.
|
||||
|
||||
Raises ReadError is not enough bits are available.
|
||||
Raises ValueError if the format is not understood.
|
||||
|
||||
See the docstring for 'read' for token examples. 'pad' tokens are skipped
|
||||
and not added to the returned list.
|
||||
|
||||
>>> h, b1, b2 = s.readlist('hex:20, bin:5, bin:3')
|
||||
>>> i, bs1, bs2 = s.readlist(['uint:12', 10, 10])
|
||||
|
||||
"""
|
||||
value, self._pos = self._readlist(fmt, self._pos, **kwargs)
|
||||
return value
|
||||
|
||||
def readto(self: TConstBitStream, bs: BitsType, /, bytealigned: Optional[bool] = None) -> TConstBitStream:
|
||||
"""Read up to and including next occurrence of bs and return result.
|
||||
|
||||
bs -- The bitstring to find.
|
||||
bytealigned -- If True the bitstring will only be
|
||||
found on byte boundaries.
|
||||
|
||||
Raises ValueError if bs is empty.
|
||||
Raises ReadError if bs is not found.
|
||||
|
||||
"""
|
||||
if isinstance(bs, numbers.Integral):
|
||||
raise ValueError("Integers cannot be searched for")
|
||||
bs = Bits._create_from_bitstype(bs)
|
||||
oldpos = self._pos
|
||||
p = self.find(bs, self._pos, bytealigned=bytealigned)
|
||||
if not p:
|
||||
raise bitstring.ReadError("Substring not found")
|
||||
self._pos += len(bs)
|
||||
return self._slice(oldpos, self._pos)
|
||||
|
||||
@overload
|
||||
def peek(self: TConstBitStream, fmt: int) -> TConstBitStream:
|
||||
...
|
||||
|
||||
@overload
|
||||
def peek(self, fmt: str) -> Union[int, float, str, TConstBitStream, bool, bytes, None]:
|
||||
...
|
||||
|
||||
def peek(self: TConstBitStream, fmt: Union[int, str]) -> Union[int, float, str, TConstBitStream, bool, bytes, None]:
|
||||
"""Interpret next bits according to format string and return result.
|
||||
|
||||
fmt -- Token string describing how to interpret the next bits.
|
||||
|
||||
The position in the bitstring is not changed. If not enough bits are
|
||||
available then all bits to the end of the bitstring will be used.
|
||||
|
||||
Raises ReadError if not enough bits are available.
|
||||
Raises ValueError if the format is not understood.
|
||||
|
||||
See the docstring for 'read' for token examples.
|
||||
|
||||
"""
|
||||
pos_before = self._pos
|
||||
value = self.read(fmt)
|
||||
self._pos = pos_before
|
||||
return value
|
||||
|
||||
def peeklist(self, fmt: Union[str, List[Union[int, str]]], **kwargs) \
|
||||
-> List[Union[int, float, str, Bits, None]]:
|
||||
"""Interpret next bits according to format string(s) and return list.
|
||||
|
||||
fmt -- One or more integers or strings with comma separated tokens describing
|
||||
how to interpret the next bits in the bitstring.
|
||||
kwargs -- A dictionary or keyword-value pairs - the keywords used in the
|
||||
format string will be replaced with their given value.
|
||||
|
||||
The position in the bitstring is not changed. If not enough bits are
|
||||
available then all bits to the end of the bitstring will be used.
|
||||
|
||||
Raises ReadError if not enough bits are available.
|
||||
Raises ValueError if the format is not understood.
|
||||
|
||||
See the docstring for 'read' for token examples.
|
||||
|
||||
"""
|
||||
pos = self._pos
|
||||
return_values = self.readlist(fmt, **kwargs)
|
||||
self._pos = pos
|
||||
return return_values
|
||||
|
||||
def bytealign(self) -> int:
|
||||
"""Align to next byte and return number of skipped bits.
|
||||
|
||||
Raises ValueError if the end of the bitstring is reached before
|
||||
aligning to the next byte.
|
||||
|
||||
"""
|
||||
skipped = (8 - (self._pos % 8)) % 8
|
||||
self.pos += skipped
|
||||
return skipped
|
||||
|
||||
@classmethod
|
||||
def fromstring(cls: TBits, s: str, /) -> TBits:
|
||||
x = super().fromstring(s)
|
||||
x._pos = 0
|
||||
x._bitstore.immutable = True
|
||||
return x
|
||||
|
||||
@overload
|
||||
def __getitem__(self: TBits, key: slice, /) -> TBits:
|
||||
...
|
||||
|
||||
@overload
|
||||
def __getitem__(self: TBits, key: int, /) -> bool:
|
||||
...
|
||||
|
||||
def __getitem__(self: TBits, key: Union[slice, int], /) -> Union[TBits, bool]:
|
||||
"""Return a new bitstring representing a slice of the current bitstring."""
|
||||
if isinstance(key, numbers.Integral):
|
||||
return bool(self._bitstore.getindex(key))
|
||||
bs = super().__new__(self.__class__)
|
||||
bs._bitstore = self._bitstore.getslice_withstep(key)
|
||||
bs._pos = 0
|
||||
return bs
|
||||
|
||||
pos = property(_getbitpos, _setbitpos,
|
||||
doc="""The position in the bitstring in bits. Read and write.
|
||||
""")
|
||||
bitpos = property(_getbitpos, _setbitpos,
|
||||
doc="""The position in the bitstring in bits. Read and write.
|
||||
""")
|
||||
bytepos = property(_getbytepos, _setbytepos,
|
||||
doc="""The position in the bitstring in bytes. Read and write.
|
||||
""")
|
||||
|
||||
|
||||
class BitStream(ConstBitStream, bitstring.BitArray):
|
||||
"""A container or stream holding a mutable sequence of bits
|
||||
|
||||
Subclass of the ConstBitStream and BitArray classes. Inherits all of
|
||||
their methods.
|
||||
|
||||
Methods:
|
||||
|
||||
all() -- Check if all specified bits are set to 1 or 0.
|
||||
any() -- Check if any of specified bits are set to 1 or 0.
|
||||
append() -- Append a bitstring.
|
||||
bytealign() -- Align to next byte boundary.
|
||||
byteswap() -- Change byte endianness in-place.
|
||||
clear() -- Remove all bits from the bitstring.
|
||||
copy() -- Return a copy of the bitstring.
|
||||
count() -- Count the number of bits set to 1 or 0.
|
||||
cut() -- Create generator of constant sized chunks.
|
||||
endswith() -- Return whether the bitstring ends with a sub-string.
|
||||
find() -- Find a sub-bitstring in the current bitstring.
|
||||
findall() -- Find all occurrences of a sub-bitstring in the current bitstring.
|
||||
fromstring() -- Create a bitstring from a formatted string.
|
||||
insert() -- Insert a bitstring.
|
||||
invert() -- Flip bit(s) between one and zero.
|
||||
join() -- Join bitstrings together using current bitstring.
|
||||
overwrite() -- Overwrite a section with a new bitstring.
|
||||
peek() -- Peek at and interpret next bits as a single item.
|
||||
peeklist() -- Peek at and interpret next bits as a list of items.
|
||||
pp() -- Pretty print the bitstring.
|
||||
prepend() -- Prepend a bitstring.
|
||||
read() -- Read and interpret next bits as a single item.
|
||||
readlist() -- Read and interpret next bits as a list of items.
|
||||
readto() -- Read up to and including next occurrence of a bitstring.
|
||||
replace() -- Replace occurrences of one bitstring with another.
|
||||
reverse() -- Reverse bits in-place.
|
||||
rfind() -- Seek backwards to find a sub-bitstring.
|
||||
rol() -- Rotate bits to the left.
|
||||
ror() -- Rotate bits to the right.
|
||||
set() -- Set bit(s) to 1 or 0.
|
||||
split() -- Create generator of chunks split by a delimiter.
|
||||
startswith() -- Return whether the bitstring starts with a sub-bitstring.
|
||||
tobitarray() -- Return bitstring as a bitarray from the bitarray package.
|
||||
tobytes() -- Return bitstring as bytes, padding if needed.
|
||||
tofile() -- Write bitstring to file, padding if needed.
|
||||
unpack() -- Interpret bits using format string.
|
||||
|
||||
Special methods:
|
||||
|
||||
Mutating operators are available: [], <<=, >>=, +=, *=, &=, |= and ^=
|
||||
in addition to [], ==, !=, +, *, ~, <<, >>, &, | and ^.
|
||||
|
||||
Properties:
|
||||
|
||||
[GENERATED_PROPERTY_DESCRIPTIONS]
|
||||
|
||||
len -- Length of the bitstring in bits.
|
||||
pos -- The current bit position in the bitstring.
|
||||
"""
|
||||
|
||||
__slots__ = ()
|
||||
|
||||
def __init__(self, auto: Optional[Union[BitsType, int]] = None, /, length: Optional[int] = None,
|
||||
offset: Optional[int] = None, pos: int = 0, **kwargs) -> None:
|
||||
"""Either specify an 'auto' initialiser:
|
||||
A string of comma separated tokens, an integer, a file object,
|
||||
a bytearray, a boolean iterable or another bitstring.
|
||||
|
||||
Or initialise via **kwargs with one (and only one) of:
|
||||
bin -- binary string representation, e.g. '0b001010'.
|
||||
hex -- hexadecimal string representation, e.g. '0x2ef'
|
||||
oct -- octal string representation, e.g. '0o777'.
|
||||
bytes -- raw data as a bytes object, for example read from a binary file.
|
||||
int -- a signed integer.
|
||||
uint -- an unsigned integer.
|
||||
float / floatbe -- a big-endian floating point number.
|
||||
bool -- a boolean (True or False).
|
||||
se -- a signed exponential-Golomb code.
|
||||
ue -- an unsigned exponential-Golomb code.
|
||||
sie -- a signed interleaved exponential-Golomb code.
|
||||
uie -- an unsigned interleaved exponential-Golomb code.
|
||||
floatle -- a little-endian floating point number.
|
||||
floatne -- a native-endian floating point number.
|
||||
bfloat / bfloatbe - a big-endian bfloat format 16-bit floating point number.
|
||||
bfloatle -- a little-endian bfloat format 16-bit floating point number.
|
||||
bfloatne -- a native-endian bfloat format 16-bit floating point number.
|
||||
intbe -- a signed big-endian whole byte integer.
|
||||
intle -- a signed little-endian whole byte integer.
|
||||
intne -- a signed native-endian whole byte integer.
|
||||
uintbe -- an unsigned big-endian whole byte integer.
|
||||
uintle -- an unsigned little-endian whole byte integer.
|
||||
uintne -- an unsigned native-endian whole byte integer.
|
||||
filename -- the path of a file which will be opened in binary read-only mode.
|
||||
|
||||
Other keyword arguments:
|
||||
length -- length of the bitstring in bits, if needed and appropriate.
|
||||
It must be supplied for all integer and float initialisers.
|
||||
offset -- bit offset to the data. These offset bits are
|
||||
ignored and this is intended for use when
|
||||
initialising using 'bytes' or 'filename'.
|
||||
pos -- Initial bit position, defaults to 0.
|
||||
|
||||
"""
|
||||
ConstBitStream.__init__(self, auto, length, offset, pos, **kwargs)
|
||||
if self._bitstore.immutable:
|
||||
self._bitstore = self._bitstore._copy()
|
||||
self._bitstore.immutable = False
|
||||
|
||||
def __copy__(self) -> BitStream:
|
||||
"""Return a new copy of the BitStream."""
|
||||
s_copy = object.__new__(BitStream)
|
||||
s_copy._pos = 0
|
||||
s_copy._bitstore = self._bitstore.copy()
|
||||
return s_copy
|
||||
|
||||
def __iadd__(self, bs: BitsType, /) -> BitStream:
|
||||
"""Append to current bitstring. Return self.
|
||||
|
||||
bs -- the bitstring to append.
|
||||
|
||||
The current bit position will be moved to the end of the BitStream.
|
||||
"""
|
||||
self._append(bs)
|
||||
self._pos = len(self)
|
||||
return self
|
||||
|
||||
def prepend(self, bs: BitsType, /) -> None:
|
||||
"""Prepend a bitstring to the current bitstring.
|
||||
|
||||
bs -- The bitstring to prepend.
|
||||
|
||||
"""
|
||||
bs = Bits._create_from_bitstype(bs)
|
||||
super().prepend(bs)
|
||||
self._pos = 0
|
||||
|
||||
def __setitem__(self, /, key: Union[slice, int], value: BitsType) -> None:
|
||||
length_before = len(self)
|
||||
super().__setitem__(key, value)
|
||||
if len(self) != length_before:
|
||||
self._pos = 0
|
||||
return
|
||||
|
||||
def __delitem__(self, /, key: Union[slice, int]) -> None:
|
||||
"""Delete item or range.
|
||||
|
||||
>>> a = BitStream('0x001122')
|
||||
>>> del a[8:16]
|
||||
>>> print a
|
||||
0x0022
|
||||
|
||||
"""
|
||||
length_before = len(self)
|
||||
self._bitstore.__delitem__(key)
|
||||
if len(self) != length_before:
|
||||
self._pos = 0
|
||||
|
||||
def insert(self, bs: BitsType, /, pos: Optional[int] = None) -> None:
|
||||
"""Insert bitstring at bit position pos.
|
||||
|
||||
bs -- The bitstring to insert.
|
||||
pos -- The bit position to insert at.
|
||||
|
||||
The current bit position will be moved to the end of the inserted section.
|
||||
Raises ValueError if pos < 0 or pos > len(self).
|
||||
|
||||
"""
|
||||
bs = Bits._create_from_bitstype(bs)
|
||||
if len(bs) == 0:
|
||||
return
|
||||
if bs is self:
|
||||
bs = self._copy()
|
||||
if pos is None:
|
||||
pos = self._pos
|
||||
if pos < 0:
|
||||
pos += len(self)
|
||||
if not 0 <= pos <= len(self):
|
||||
raise ValueError("Invalid insert position.")
|
||||
self._insert(bs, pos)
|
||||
self._pos = pos + len(bs)
|
||||
|
||||
def replace(self, old: BitsType, new: BitsType, start: Optional[int] = None, end: Optional[int] = None,
|
||||
count: Optional[int] = None, bytealigned: Optional[bool] = None) -> int:
|
||||
"""Replace all occurrences of old with new in place.
|
||||
|
||||
Returns number of replacements made.
|
||||
|
||||
old -- The bitstring to replace.
|
||||
new -- The replacement bitstring.
|
||||
start -- Any occurrences that start before this will not be replaced.
|
||||
Defaults to 0.
|
||||
end -- Any occurrences that finish after this will not be replaced.
|
||||
Defaults to len(self).
|
||||
count -- The maximum number of replacements to make. Defaults to
|
||||
replace all occurrences.
|
||||
bytealigned -- If True replacements will only be made on byte
|
||||
boundaries.
|
||||
|
||||
Raises ValueError if old is empty or if start or end are
|
||||
out of range.
|
||||
|
||||
"""
|
||||
if count == 0:
|
||||
return 0
|
||||
if len(old := Bits._create_from_bitstype(old)) == 0:
|
||||
raise ValueError("Empty bitstring cannot be replaced.")
|
||||
start, end = self._validate_slice(start, end)
|
||||
new = Bits._create_from_bitstype(new)
|
||||
if new is self:
|
||||
# Prevent self assignment woes
|
||||
new = copy.copy(self)
|
||||
length_before = len(self)
|
||||
replacement_count = self._replace(old, new, start, end, 0 if count is None else count, bytealigned)
|
||||
if len(self) != length_before:
|
||||
self._pos = 0
|
||||
return replacement_count
|
||||
@@ -0,0 +1,95 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import bitstring
|
||||
import os
|
||||
|
||||
|
||||
class Options:
|
||||
"""Internal class to create singleton module options instance."""
|
||||
|
||||
_instance = None
|
||||
|
||||
def __init__(self):
|
||||
self.set_lsb0(False)
|
||||
self._bytealigned = False
|
||||
self.mxfp_overflow = 'saturate'
|
||||
|
||||
self.no_color = False
|
||||
no_color = os.getenv('NO_COLOR')
|
||||
self.no_color = True if no_color else False
|
||||
|
||||
@property
|
||||
def mxfp_overflow(self) -> str:
|
||||
return self._mxfp_overflow
|
||||
|
||||
@mxfp_overflow.setter
|
||||
def mxfp_overflow(self, value: str) -> None:
|
||||
allowed_values = ('saturate', 'overflow')
|
||||
if value not in allowed_values:
|
||||
raise ValueError(f"mxfp_overflow must be one of {allowed_values}, not {value}.")
|
||||
self._mxfp_overflow = value
|
||||
|
||||
def __repr__(self) -> str:
|
||||
attributes = {attr: getattr(self, attr) for attr in dir(self) if not attr.startswith('_') and not callable(getattr(self, attr))}
|
||||
return '\n'.join(f"{attr}: {value!r}" for attr, value in attributes.items())
|
||||
|
||||
@property
|
||||
def lsb0(self) -> bool:
|
||||
return self._lsb0
|
||||
|
||||
@lsb0.setter
|
||||
def lsb0(self, value: bool) -> None:
|
||||
self.set_lsb0(value)
|
||||
|
||||
def set_lsb0(self, value: bool) -> None:
|
||||
self._lsb0 = bool(value)
|
||||
Bits = bitstring.bits.Bits
|
||||
BitArray = bitstring.bitarray_.BitArray
|
||||
BitStore = bitstring.bitstore.BitStore
|
||||
|
||||
lsb0_methods = {
|
||||
Bits: {'_find': Bits._find_lsb0, '_rfind': Bits._rfind_lsb0, '_findall': Bits._findall_lsb0},
|
||||
BitArray: {'_ror': BitArray._rol_msb0, '_rol': BitArray._ror_msb0, '_append': BitArray._append_lsb0,
|
||||
'_prepend': BitArray._append_msb0},
|
||||
BitStore: {'__setitem__': BitStore.setitem_lsb0, '__delitem__': BitStore.delitem_lsb0,
|
||||
'getindex': BitStore.getindex_lsb0, 'getslice': BitStore.getslice_lsb0,
|
||||
'getslice_withstep': BitStore.getslice_withstep_lsb0, 'invert': BitStore.invert_lsb0}
|
||||
}
|
||||
msb0_methods = {
|
||||
Bits: {'_find': Bits._find_msb0, '_rfind': Bits._rfind_msb0, '_findall': Bits._findall_msb0},
|
||||
BitArray: {'_ror': BitArray._ror_msb0, '_rol': BitArray._rol_msb0, '_append': BitArray._append_msb0,
|
||||
'_prepend': BitArray._append_lsb0},
|
||||
BitStore: {'__setitem__': BitStore.setitem_msb0, '__delitem__': BitStore.delitem_msb0,
|
||||
'getindex': BitStore.getindex_msb0, 'getslice': BitStore.getslice_msb0,
|
||||
'getslice_withstep': BitStore.getslice_withstep_msb0, 'invert': BitStore.invert_msb0}
|
||||
}
|
||||
methods = lsb0_methods if self._lsb0 else msb0_methods
|
||||
for cls, method_dict in methods.items():
|
||||
for attr, method in method_dict.items():
|
||||
setattr(cls, attr, method)
|
||||
|
||||
@property
|
||||
def bytealigned(self) -> bool:
|
||||
return self._bytealigned
|
||||
|
||||
@bytealigned.setter
|
||||
def bytealigned(self, value: bool) -> None:
|
||||
self._bytealigned = bool(value)
|
||||
|
||||
def __new__(cls):
|
||||
if cls._instance is None:
|
||||
cls._instance = super(Options, cls).__new__(cls)
|
||||
return cls._instance
|
||||
|
||||
|
||||
class Colour:
|
||||
def __new__(cls, use_colour: bool) -> Colour:
|
||||
x = super().__new__(cls)
|
||||
if use_colour:
|
||||
cls.blue = '\033[34m'
|
||||
cls.purple = '\033[35m'
|
||||
cls.green = '\033[32m'
|
||||
cls.off = '\033[0m'
|
||||
else:
|
||||
cls.blue = cls.purple = cls.green = cls.off = ''
|
||||
return x
|
||||
403
code/.venv/lib/python3.12/site-packages/bitstring/dtypes.py
Normal file
403
code/.venv/lib/python3.12/site-packages/bitstring/dtypes.py
Normal file
@@ -0,0 +1,403 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import functools
|
||||
from typing import Optional, Dict, Any, Union, Tuple, Callable
|
||||
import inspect
|
||||
import bitstring
|
||||
from bitstring import utils
|
||||
|
||||
CACHE_SIZE = 256
|
||||
|
||||
|
||||
def scaled_get_fn(get_fn, s: Union[int, float]):
|
||||
def wrapper(*args, scale=s, **kwargs):
|
||||
return get_fn(*args, **kwargs) * scale
|
||||
return wrapper
|
||||
|
||||
|
||||
def scaled_set_fn(set_fn, s: Union[int, float]):
|
||||
def wrapper(bs, value, *args, scale=s, **kwargs):
|
||||
return set_fn(bs, value / scale, *args, **kwargs)
|
||||
return wrapper
|
||||
|
||||
|
||||
def scaled_read_fn(read_fn, s: Union[int, float]):
|
||||
def wrapper(*args, scale=s, **kwargs):
|
||||
val = read_fn(*args, **kwargs)
|
||||
if isinstance(val, tuple):
|
||||
val, pos = val
|
||||
return val * scale, pos
|
||||
return val * scale
|
||||
return wrapper
|
||||
|
||||
|
||||
class Dtype:
|
||||
"""A data type class, representing a concrete interpretation of binary data.
|
||||
|
||||
Dtype instances are immutable. They are often created implicitly elsewhere via a token string.
|
||||
|
||||
>>> u12 = Dtype('uint', 12) # length separate from token string.
|
||||
>>> float16 = Dtype('float16') # length part of token string.
|
||||
>>> mxfp = Dtype('e3m2mxfp', scale=2 ** 6) # dtype with scaling factor
|
||||
|
||||
"""
|
||||
|
||||
_name: str
|
||||
_read_fn: Callable
|
||||
_set_fn: Callable
|
||||
_get_fn: Callable
|
||||
_return_type: Any
|
||||
_is_signed: bool
|
||||
_set_fn_needs_length: bool
|
||||
_variable_length: bool
|
||||
_bitlength: Optional[int]
|
||||
_bits_per_item: int
|
||||
_length: Optional[int]
|
||||
_scale: Union[None, float, int]
|
||||
|
||||
def __new__(cls, token: Union[str, Dtype], /, length: Optional[int] = None, scale: Union[None, float, int] = None) -> Dtype:
|
||||
if isinstance(token, cls):
|
||||
return token
|
||||
if length is None:
|
||||
x = cls._new_from_token(token, scale)
|
||||
return x
|
||||
else:
|
||||
x = dtype_register.get_dtype(token, length, scale)
|
||||
return x
|
||||
|
||||
@property
|
||||
def scale(self) -> Union[int, float, None]:
|
||||
"""The multiplicative scale applied when interpreting the data."""
|
||||
return self._scale
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
"""A string giving the name of the data type."""
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def length(self) -> int:
|
||||
"""The length of the data type in units of bits_per_item. Set to None for variable length dtypes."""
|
||||
return self._length
|
||||
|
||||
@property
|
||||
def bitlength(self) -> Optional[int]:
|
||||
"""The number of bits needed to represent a single instance of the data type. Set to None for variable length dtypes."""
|
||||
return self._bitlength
|
||||
|
||||
@property
|
||||
def bits_per_item(self) -> int:
|
||||
"""The number of bits for each unit of length. Usually 1, but equals 8 for bytes type."""
|
||||
return self._bits_per_item
|
||||
|
||||
@property
|
||||
def variable_length(self) -> bool:
|
||||
"""If True then the length of the data type depends on the data being interpreted, and must not be specified."""
|
||||
return self._variable_length
|
||||
|
||||
@property
|
||||
def return_type(self) -> Any:
|
||||
"""The type of the value returned by the parse method, such as int, float or str."""
|
||||
return self._return_type
|
||||
|
||||
@property
|
||||
def is_signed(self) -> bool:
|
||||
"""If True then the data type represents a signed quantity."""
|
||||
return self._is_signed
|
||||
|
||||
@property
|
||||
def set_fn(self) -> Optional[Callable]:
|
||||
"""A function to set the value of the data type."""
|
||||
return self._set_fn
|
||||
|
||||
@property
|
||||
def get_fn(self) -> Callable:
|
||||
"""A function to get the value of the data type."""
|
||||
return self._get_fn
|
||||
|
||||
@property
|
||||
def read_fn(self) -> Callable:
|
||||
"""A function to read the value of the data type."""
|
||||
return self._read_fn
|
||||
|
||||
def _set_scale(self, value: Union[None, float, int]) -> None:
|
||||
self._scale = value
|
||||
if self._scale is None:
|
||||
return
|
||||
if self._scale == 0:
|
||||
raise ValueError("A Dtype's scale factor must not be zero.")
|
||||
if not hasattr(self, 'unscaled_get_fn'):
|
||||
self.unscaled_get_fn = self._get_fn
|
||||
self.unscaled_set_fn = self._set_fn
|
||||
self.unscaled_read_fn = self._read_fn
|
||||
self._get_fn = scaled_get_fn(self.unscaled_get_fn, self._scale)
|
||||
self._set_fn = scaled_set_fn(self.unscaled_set_fn, self._scale)
|
||||
self._read_fn = scaled_read_fn(self.unscaled_read_fn, self._scale)
|
||||
|
||||
@classmethod
|
||||
@functools.lru_cache(CACHE_SIZE)
|
||||
def _new_from_token(cls, token: str, scale: Union[None, float, int] = None) -> Dtype:
|
||||
token = ''.join(token.split())
|
||||
return dtype_register.get_dtype(*utils.parse_name_length_token(token), scale=scale)
|
||||
|
||||
def __hash__(self) -> int:
|
||||
return hash((self._name, self._length))
|
||||
|
||||
@classmethod
|
||||
@functools.lru_cache(CACHE_SIZE)
|
||||
def _create(cls, definition: DtypeDefinition, length: Optional[int], scale: Union[None, float, int]) -> Dtype:
|
||||
x = super().__new__(cls)
|
||||
x._name = definition.name
|
||||
x._bitlength = x._length = length
|
||||
x._bits_per_item = definition.multiplier
|
||||
if x._bitlength is not None:
|
||||
x._bitlength *= x._bits_per_item
|
||||
x._set_fn_needs_length = definition.set_fn_needs_length
|
||||
x._variable_length = definition.variable_length
|
||||
if x._variable_length or dtype_register.names[x._name].allowed_lengths.only_one_value():
|
||||
x._read_fn = definition.read_fn
|
||||
else:
|
||||
x._read_fn = functools.partial(definition.read_fn, length=x._bitlength)
|
||||
if definition.set_fn is None:
|
||||
x._set_fn = None
|
||||
else:
|
||||
if x._set_fn_needs_length:
|
||||
x._set_fn = functools.partial(definition.set_fn, length=x._bitlength)
|
||||
else:
|
||||
x._set_fn = definition.set_fn
|
||||
x._get_fn = definition.get_fn
|
||||
x._return_type = definition.return_type
|
||||
x._is_signed = definition.is_signed
|
||||
x._set_scale(scale)
|
||||
return x
|
||||
|
||||
def build(self, value: Any, /) -> bitstring.Bits:
|
||||
"""Create a bitstring from a value.
|
||||
|
||||
The value parameter should be of a type appropriate to the dtype.
|
||||
"""
|
||||
b = bitstring.Bits()
|
||||
self._set_fn(b, value)
|
||||
if self.bitlength is not None and len(b) != self.bitlength:
|
||||
raise ValueError(f"Dtype has a length of {self.bitlength} bits, but value '{value}' has {len(b)} bits.")
|
||||
return b
|
||||
|
||||
def parse(self, b: BitsType, /) -> Any:
|
||||
"""Parse a bitstring to find its value.
|
||||
|
||||
The b parameter should be a bitstring of the appropriate length, or an object that can be converted to a bitstring."""
|
||||
b = bitstring.Bits._create_from_bitstype(b)
|
||||
return self._get_fn(bitstring.Bits(b))
|
||||
|
||||
def __str__(self) -> str:
|
||||
if self._scale is not None:
|
||||
return self.__repr__()
|
||||
hide_length = self._variable_length or dtype_register.names[self._name].allowed_lengths.only_one_value() or self._length is None
|
||||
length_str = '' if hide_length else str(self._length)
|
||||
return f"{self._name}{length_str}"
|
||||
|
||||
def __repr__(self) -> str:
|
||||
hide_length = self._variable_length or dtype_register.names[self._name].allowed_lengths.only_one_value() or self._length is None
|
||||
length_str = '' if hide_length else ', ' + str(self._length)
|
||||
if self._scale is None:
|
||||
scale_str = ''
|
||||
else:
|
||||
try:
|
||||
# This will only succeed for powers of two from -127 to 127.
|
||||
e8m0 = bitstring.Bits(e8m0mxfp=self._scale)
|
||||
except ValueError:
|
||||
scale_str = f', scale={self._scale}'
|
||||
else:
|
||||
power_of_two = e8m0.uint - 127
|
||||
if power_of_two in [0, 1]:
|
||||
scale_str = f', scale={self._scale}'
|
||||
else:
|
||||
scale_str = f', scale=2 ** {power_of_two}'
|
||||
return f"{self.__class__.__name__}('{self._name}'{length_str}{scale_str})"
|
||||
|
||||
def __eq__(self, other: Any) -> bool:
|
||||
if isinstance(other, Dtype):
|
||||
return self._name == other._name and self._length == other._length
|
||||
return False
|
||||
|
||||
|
||||
class AllowedLengths:
|
||||
def __init__(self, value: Tuple[int, ...] = tuple()) -> None:
|
||||
if len(value) >= 3 and value[-1] is Ellipsis:
|
||||
step = value[1] - value[0]
|
||||
for i in range(1, len(value) - 1):
|
||||
if value[i] - value[i - 1] != step:
|
||||
raise ValueError(f"Allowed length tuples must be equally spaced when final element is Ellipsis, but got {value}.")
|
||||
self.values = (value[0], value[1], Ellipsis)
|
||||
else:
|
||||
self.values = value
|
||||
|
||||
def __str__(self) -> str:
|
||||
if self.values and self.values[-1] is Ellipsis:
|
||||
return f"({self.values[0]}, {self.values[1]}, ...)"
|
||||
return str(self.values)
|
||||
|
||||
def __contains__(self, other: Any) -> bool:
|
||||
if not self.values:
|
||||
return True
|
||||
if self.values[-1] is Ellipsis:
|
||||
return (other - self.values[0]) % (self.values[1] - self.values[0]) == 0
|
||||
return other in self.values
|
||||
|
||||
def only_one_value(self) -> bool:
|
||||
return self.values and len(self.values) == 1
|
||||
|
||||
|
||||
class DtypeDefinition:
|
||||
"""Represents a class of dtypes, such as uint or float, rather than a concrete dtype such as uint8.
|
||||
Not (yet) part of the public interface."""
|
||||
|
||||
def __init__(self, name: str, set_fn, get_fn, return_type: Any = Any, is_signed: bool = False, bitlength2chars_fn=None,
|
||||
variable_length: bool = False, allowed_lengths: Tuple[int, ...] = tuple(), multiplier: int = 1, description: str = ''):
|
||||
|
||||
# Consistency checks
|
||||
if int(multiplier) != multiplier or multiplier <= 0:
|
||||
raise ValueError("multiplier must be an positive integer")
|
||||
if variable_length and allowed_lengths:
|
||||
raise ValueError("A variable length dtype can't have allowed lengths.")
|
||||
if variable_length and set_fn is not None and 'length' in inspect.signature(set_fn).parameters:
|
||||
raise ValueError("A variable length dtype can't have a set_fn which takes a length.")
|
||||
|
||||
self.name = name
|
||||
self.description = description
|
||||
self.return_type = return_type
|
||||
self.is_signed = is_signed
|
||||
self.variable_length = variable_length
|
||||
self.allowed_lengths = AllowedLengths(allowed_lengths)
|
||||
|
||||
self.multiplier = multiplier
|
||||
|
||||
# Can work out if set_fn needs length based on its signature.
|
||||
self.set_fn_needs_length = set_fn is not None and 'length' in inspect.signature(set_fn).parameters
|
||||
self.set_fn = set_fn
|
||||
|
||||
if self.allowed_lengths.values:
|
||||
def allowed_length_checked_get_fn(bs):
|
||||
if len(bs) not in self.allowed_lengths:
|
||||
if self.allowed_lengths.only_one_value():
|
||||
raise bitstring.InterpretError(f"'{self.name}' dtypes must have a length of {self.allowed_lengths.values[0]}, but received a length of {len(bs)}.")
|
||||
else:
|
||||
raise bitstring.InterpretError(f"'{self.name}' dtypes must have a length in {self.allowed_lengths}, but received a length of {len(bs)}.")
|
||||
return get_fn(bs)
|
||||
self.get_fn = allowed_length_checked_get_fn # Interpret everything and check the length
|
||||
else:
|
||||
self.get_fn = get_fn # Interpret everything
|
||||
|
||||
# Create a reading function from the get_fn.
|
||||
if not self.variable_length:
|
||||
if self.allowed_lengths.only_one_value():
|
||||
def read_fn(bs, start):
|
||||
return self.get_fn(bs[start:start + self.allowed_lengths.values[0]])
|
||||
else:
|
||||
def read_fn(bs, start, length):
|
||||
if len(bs) < start + length:
|
||||
raise bitstring.ReadError(f"Needed a length of at least {length} bits, but only {len(bs) - start} bits were available.")
|
||||
return self.get_fn(bs[start:start + length])
|
||||
self.read_fn = read_fn
|
||||
else:
|
||||
# We only find out the length when we read/get.
|
||||
def length_checked_get_fn(bs):
|
||||
x, length = get_fn(bs)
|
||||
if length != len(bs):
|
||||
raise ValueError
|
||||
return x
|
||||
self.get_fn = length_checked_get_fn
|
||||
|
||||
def read_fn(bs, start):
|
||||
try:
|
||||
x, length = get_fn(bs[start:])
|
||||
except bitstring.InterpretError:
|
||||
raise bitstring.ReadError
|
||||
return x, start + length
|
||||
self.read_fn = read_fn
|
||||
self.bitlength2chars_fn = bitlength2chars_fn
|
||||
|
||||
def get_dtype(self, length: Optional[int] = None, scale: Union[None, float, int] = None) -> Dtype:
|
||||
if self.allowed_lengths:
|
||||
if length is None:
|
||||
if self.allowed_lengths.only_one_value():
|
||||
length = self.allowed_lengths.values[0]
|
||||
else:
|
||||
if length not in self.allowed_lengths:
|
||||
if self.allowed_lengths.only_one_value():
|
||||
raise ValueError(f"A length of {length} was supplied for the '{self.name}' dtype, but its only allowed length is {self.allowed_lengths.values[0]}.")
|
||||
else:
|
||||
raise ValueError(f"A length of {length} was supplied for the '{self.name}' dtype which is not one of its possible lengths (must be one of {self.allowed_lengths}).")
|
||||
if length is None:
|
||||
d = Dtype._create(self, None, scale)
|
||||
return d
|
||||
if self.variable_length:
|
||||
raise ValueError(f"A length ({length}) shouldn't be supplied for the variable length dtype '{self.name}'.")
|
||||
d = Dtype._create(self, length, scale)
|
||||
return d
|
||||
|
||||
def __repr__(self) -> str:
|
||||
s = f"{self.__class__.__name__}(name='{self.name}', description='{self.description}', return_type={self.return_type.__name__}, "
|
||||
s += f"is_signed={self.is_signed}, set_fn_needs_length={self.set_fn_needs_length}, allowed_lengths={self.allowed_lengths!s}, multiplier={self.multiplier})"
|
||||
return s
|
||||
|
||||
|
||||
class Register:
|
||||
"""A singleton class that holds all the DtypeDefinitions. Not (yet) part of the public interface."""
|
||||
|
||||
_instance: Optional[Register] = None
|
||||
names: Dict[str, DtypeDefinition] = {}
|
||||
|
||||
def __new__(cls) -> Register:
|
||||
# Singleton. Only one Register instance can ever exist.
|
||||
if cls._instance is None:
|
||||
cls._instance = super(Register, cls).__new__(cls)
|
||||
return cls._instance
|
||||
|
||||
@classmethod
|
||||
def add_dtype(cls, definition: DtypeDefinition):
|
||||
cls.names[definition.name] = definition
|
||||
if definition.get_fn is not None:
|
||||
setattr(bitstring.bits.Bits, definition.name, property(fget=definition.get_fn, doc=f"The bitstring as {definition.description}. Read only."))
|
||||
if definition.set_fn is not None:
|
||||
setattr(bitstring.bitarray_.BitArray, definition.name, property(fget=definition.get_fn, fset=definition.set_fn, doc=f"The bitstring as {definition.description}. Read and write."))
|
||||
|
||||
@classmethod
|
||||
def add_dtype_alias(cls, name: str, alias: str):
|
||||
cls.names[alias] = cls.names[name]
|
||||
definition = cls.names[alias]
|
||||
if definition.get_fn is not None:
|
||||
setattr(bitstring.bits.Bits, alias, property(fget=definition.get_fn, doc=f"An alias for '{name}'. Read only."))
|
||||
if definition.set_fn is not None:
|
||||
setattr(bitstring.bitarray_.BitArray, alias, property(fget=definition.get_fn, fset=definition.set_fn, doc=f"An alias for '{name}'. Read and write."))
|
||||
|
||||
@classmethod
|
||||
def get_dtype(cls, name: str, length: Optional[int], scale: Union[None, float, int] = None) -> Dtype:
|
||||
try:
|
||||
definition = cls.names[name]
|
||||
except KeyError:
|
||||
raise ValueError(f"Unknown Dtype name '{name}'. Names available: {list(cls.names.keys())}.")
|
||||
else:
|
||||
return definition.get_dtype(length, scale)
|
||||
|
||||
@classmethod
|
||||
def __getitem__(cls, name: str) -> DtypeDefinition:
|
||||
return cls.names[name]
|
||||
|
||||
@classmethod
|
||||
def __delitem__(cls, name: str) -> None:
|
||||
del cls.names[name]
|
||||
|
||||
def __repr__(self) -> str:
|
||||
s = [f"{'key':<12}:{'name':^12}{'signed':^8}{'set_fn_needs_length':^23}{'allowed_lengths':^16}{'multiplier':^12}{'return_type':<13}"]
|
||||
s.append('-' * 85)
|
||||
for key in self.names:
|
||||
m = self.names[key]
|
||||
allowed = '' if not m.allowed_lengths else m.allowed_lengths
|
||||
ret = 'None' if m.return_type is None else m.return_type.__name__
|
||||
s.append(f"{key:<12}:{m.name:>12}{m.is_signed:^8}{m.set_fn_needs_length:^16}{allowed!s:^16}{m.multiplier:^12}{ret:<13} # {m.description}")
|
||||
return '\n'.join(s)
|
||||
|
||||
|
||||
# Create the Register singleton
|
||||
dtype_register = Register()
|
||||
@@ -0,0 +1,23 @@
|
||||
|
||||
class Error(Exception):
|
||||
"""Base class for errors in the bitstring module."""
|
||||
|
||||
def __init__(self, *params: object) -> None:
|
||||
self.msg = params[0] if params else ''
|
||||
self.params = params[1:]
|
||||
|
||||
|
||||
class ReadError(Error, IndexError):
|
||||
"""Reading or peeking past the end of a bitstring."""
|
||||
|
||||
|
||||
InterpretError = ValueError
|
||||
"""Inappropriate interpretation of binary data."""
|
||||
|
||||
|
||||
class ByteAlignError(Error):
|
||||
"""Whole-byte position or length needed."""
|
||||
|
||||
|
||||
CreationError = ValueError
|
||||
"""Inappropriate argument during bitstring creation."""
|
||||
97
code/.venv/lib/python3.12/site-packages/bitstring/fp8.py
Normal file
97
code/.venv/lib/python3.12/site-packages/bitstring/fp8.py
Normal file
@@ -0,0 +1,97 @@
|
||||
"""
|
||||
The 8-bit float formats used here are from a proposal supported by Graphcore, AMD and Qualcomm.
|
||||
See https://arxiv.org/abs/2206.02915
|
||||
|
||||
"""
|
||||
|
||||
import struct
|
||||
import zlib
|
||||
import array
|
||||
import bitarray
|
||||
from bitstring.luts import binary8_luts_compressed
|
||||
import math
|
||||
|
||||
|
||||
class Binary8Format:
|
||||
"""8-bit floating point formats based on draft IEEE binary8"""
|
||||
|
||||
def __init__(self, exp_bits: int, bias: int):
|
||||
self.exp_bits = exp_bits
|
||||
self.bias = bias
|
||||
self.pos_clamp_value = 0b01111111
|
||||
self.neg_clamp_value = 0b11111111
|
||||
|
||||
def __str__(self):
|
||||
return f"Binary8Format(exp_bits={self.exp_bits}, bias={self.bias})"
|
||||
|
||||
def decompress_luts(self):
|
||||
binary8_to_float_compressed, float16_to_binary8_compressed = binary8_luts_compressed[(self.exp_bits, self.bias)]
|
||||
self.lut_float16_to_binary8 = zlib.decompress(float16_to_binary8_compressed)
|
||||
dec = zlib.decompress(binary8_to_float_compressed)
|
||||
self.lut_binary8_to_float = struct.unpack(f'<{len(dec) // 4}f', dec)
|
||||
|
||||
def create_luts(self):
|
||||
self.lut_binary8_to_float = self.createLUT_for_binary8_to_float()
|
||||
self.lut_float16_to_binary8 = self.createLUT_for_float16_to_binary8()
|
||||
|
||||
def float_to_int8(self, f: float) -> int:
|
||||
"""Given a Python float convert to the best float8 (expressed as an integer in 0-255 range)."""
|
||||
# First convert the float to a float16, then a 16 bit uint
|
||||
try:
|
||||
b = struct.pack('>e', f)
|
||||
except (OverflowError, struct.error):
|
||||
# Return the largest representable positive or negative value
|
||||
return self.pos_clamp_value if f > 0 else self.neg_clamp_value
|
||||
f16_int = int.from_bytes(b, byteorder='big')
|
||||
# Then use this as an index to our large LUT
|
||||
return self.lut_float16_to_binary8[f16_int]
|
||||
|
||||
def createLUT_for_float16_to_binary8(self) -> bytes:
|
||||
# Used to create the LUT that was compressed and stored for the fp8 code
|
||||
import gfloat
|
||||
fi = gfloat.formats.format_info_p3109(8 - self.exp_bits)
|
||||
fp16_to_fp8 = bytearray(1 << 16)
|
||||
for i in range(1 << 16):
|
||||
b = struct.pack('>H', i)
|
||||
f, = struct.unpack('>e', b)
|
||||
fp = gfloat.round_float(fi, f)
|
||||
if math.isnan(fp):
|
||||
fp8_i = 0b10000000
|
||||
else:
|
||||
fp8_i = self.lut_binary8_to_float.index(fp)
|
||||
fp16_to_fp8[i] = fp8_i
|
||||
return bytes(fp16_to_fp8)
|
||||
|
||||
def createLUT_for_binary8_to_float(self):
|
||||
"""Create a LUT to convert an int in range 0-255 representing a float8 into a Python float"""
|
||||
i2f = []
|
||||
for i in range(256):
|
||||
b = bitarray.util.int2ba(i, length=8, endian='big', signed=False)
|
||||
sign = b[0]
|
||||
exponent = bitarray.util.ba2int(b[1:1 + self.exp_bits])
|
||||
significand = b[1 + self.exp_bits:]
|
||||
if exponent == 0:
|
||||
significand = bitarray.bitarray('0') + significand
|
||||
exponent = -self.bias + 1
|
||||
else:
|
||||
significand = bitarray.bitarray('1') + significand
|
||||
exponent -= self.bias
|
||||
f = float(bitarray.util.ba2int(significand)) / (2.0 ** (7 - self.exp_bits))
|
||||
f *= 2 ** exponent
|
||||
i2f.append(f if not sign else -f)
|
||||
# One special case for minus zero
|
||||
i2f[0b10000000] = float('nan')
|
||||
# and for plus and minus infinity
|
||||
i2f[0b01111111] = float('inf')
|
||||
i2f[0b11111111] = float('-inf')
|
||||
return array.array('f', i2f)
|
||||
|
||||
|
||||
# We create the 1.5.2 and 1.4.3 formats.
|
||||
p4binary_fmt = Binary8Format(exp_bits=4, bias=8)
|
||||
p3binary_fmt = Binary8Format(exp_bits=5, bias=16)
|
||||
|
||||
|
||||
def decompress_luts():
|
||||
p4binary_fmt.decompress_luts()
|
||||
p3binary_fmt.decompress_luts()
|
||||
245
code/.venv/lib/python3.12/site-packages/bitstring/luts.py
Normal file
245
code/.venv/lib/python3.12/site-packages/bitstring/luts.py
Normal file
@@ -0,0 +1,245 @@
|
||||
#
|
||||
# This file is generated by generate_luts.py. DO NOT EDIT.
|
||||
#
|
||||
|
||||
mxfp_luts_compressed = \
|
||||
{(2, 1, 1, 'saturate'): (b'x\x01\x1d\xc9\xc1\r\xc00\x00\xc2@o\xd6\x8c\xc6f\xf5h\xb1\x828\xf1\x00^>X\x0c\xa7f1,\x7f'
|
||||
b'\x13\x83\xfdY\xf4\x027\xf1\x0c\xfb',
|
||||
b"x\x01\xed\xdd\t\r\xc00\x10\x03\xc1\xf4\xff[\xfehS\x1a'y\x96\x81\x87\x80[\x13\x01\x02"
|
||||
b'\x04\x08\x10 @\x80\x00\x01\x02\x04\x08\x10 @\x80\x00\x01\x02\x04\x08\x10 @\x80'
|
||||
b'\x00\x01\x02\x04\x08\x10 @\x80\x00\x01\x02\x04\x08\x10 @\x80\x00\x01\x02\x04\x08\x10'
|
||||
b' @\x80\x00\x01\x02e\x05\x06\x11 @\x80@\xa2\xc0(\x02\xc9\x02\x93\x92\x05fe\x0b,J\x16X\x95-'
|
||||
b'\xb0\x89\x00\x01\x02\x04\x08\x10 @\x80\x00\x01\x02\x04\x08\x10 @\x80\x00\x01\x02\x04'
|
||||
b'\x08\x10 @\x80\x00\x01\x02\x04\x08\x10 @\x80\x00\x01\x02\x04\x08\x10 @\x80\x00'
|
||||
b'\x01\x02\x04\x08\x10 @\x80\x00\x01\x02\x04\x08\x14\x14\xe8"@ U`\x17\x01\x02\x04\x08\x10 '
|
||||
b'@\x80\x00\x01\x02\x04\x08\x10 @\x80\x00\x01\x02\x04\x08\x10 @\x80\x00\x01\x02\x04\x08\x10 @'
|
||||
b'\x80\x00\x01\x02\x04\x08\x10 @\x80\x00\x01\x02\x04\x08\x10 @\x80\x00\x01\x02u\x05\x0e\x11 @'
|
||||
b'\x80@\xa2\xc0)\x02\xc9\x02\x97\x92\x05ne\x0b<J\x16x\x95-\xf0\x89\x00\x01\x02\x04\x08\x10'
|
||||
b' @\x80\x00\x01\x02\x04\x08\x10 @\x80\x00\x01\x02\x04\x08\x10 @\x80\x00\x01\x02\x04\x08\x10 '
|
||||
b'@\x80\x00\x01\x02\x04\x08\x10 @\x80\x00\x01\x02\x04\x08\x10 @\x80\x00\x01\x02\x04'
|
||||
b'\x08\x14\x14H=>\xb7\x9b\x00\x81\xfe\x03\\M8\xf2'),
|
||||
(2, 3, 1, 'saturate'): (b'x\x01\x1d\xcdQ\r\xc30\x10\x03\xd0\x830\x08\x85P\x04[ \x14B \x0cB \x04B \x14\xc1v\x10'
|
||||
b'\x02!\x10\x02aO\xb3\xf4\xe4?;\xe2\x9fgD#\x89W\xc4A\xa1\xd2\xe8\x0cn\x92\xc9b\x13%\xe2\xc1'
|
||||
b'\xc1I\xe1\xa2\xf2\xa6\xd1\x19\xdc$\x93\xc5&\x1a\x1fE\x12_[\x14*\x8d\xce\xe0&\x99,6\x91\xfe98'
|
||||
b')\\T\xde4:\x83\x9bd\xb2\xd8\xf9\x03~S=\xdd',
|
||||
b'x\x01\xed\xdd\x85qB\x01\x14DQ\x12\x88\x11#\xeeF\x84\xb8\xbb\xf6_\x15\xa9\x82\xd9\xf9\xf3'
|
||||
b'\xce\xed`O\x03\xdbj\x89\x00\x01\x02\x04\x08\x10 @\x80\x00\x01\x02\x04\x08\x10 @\x80\x00\x01'
|
||||
b'\x02\x04\x08\x10 @\x80\x00\x01\x02\x04\x08\x10 @\x80\x00\x01\x02\x04\x08\x10 @'
|
||||
b'\x80\x00\x81\xc6\x08\x8c\x89\x00\x01\x02\x04*\n\x8c\x8b@e\x81\xb6*\x0bt\x94\x15\x98\xa8\xddd'
|
||||
b'\xf5\xa6j7\xdd\xf4f\xb2u\xd3\xcdf\x9bK7\x9fm!\xddb\xb6^\xba\xa5l\xcb\xe9V\xb2\xad\xa6['
|
||||
b'\xcb\xb6\x9en#\xdbf\xba\xadl\xdb\xe9v\xb2\xed\xa6\xdb\xcb\xb6\x9f\xee@\x04\x08\x10 @\x80'
|
||||
b'\x00\x01\x02\x04\x08\x10 @\x80\x00\x01\x02\x04\x08\x10 @\x80\x00\x01\x02\x04\x08\x10'
|
||||
b' @\x80\x00\x01\x02\x04\x08\x10 @\x80\x00\x01\x02\x04\x08\x10 @\x80\x00\x01\x02'
|
||||
b'\x04\x08\x10\x18\x91\xc0P\x04\x08T\x158\x14\x01\x02\x04\x08\x10 @\x80\x00\x01\x02'
|
||||
b'\x04\x08\x10 @\x80\x00\x01\x02\x04\x08\x10 @\x80\x00\x01\x02\x04\x08\x10 @\x80'
|
||||
b'\x00\x01\x02\x04\x08\x10 @\x80\x00\x01\x02\xcd\x118\x12\x01\x02\x04\x08T\x14\xe8\x8b@e\x81c'
|
||||
b'U\x168QV\xe0\xb4vg\xd5\x1b\xd4\xee\xbc\xe9]d\xbbLw\x95\xed:\xddM\xb6\xdbtw\xd9\xee\xd3=d{L'
|
||||
b'\xf7\x94\xed9\xddK\xb6\xd7to\xd9\xde\xd3}d\xfbL\xf7\x95\xed;\xddO\xb6\xdft\x7f"@\x80\x00\x01'
|
||||
b'\x02\x04\x08\x10 @\x80\x00\x01\x02\x04\x08\x10 @\x80\x00\x01\x02\x04\x08\x10 @'
|
||||
b'\x80\x00\x01\x02\x04\x08\x10 @\x80\x00\x01\x02\x04\x08\x10 @\x80\x00\x01\x02\x04\x08'
|
||||
b'\x10 @\x80\xc0\x88\x04\xaa\x1e\x9f\xdbM\x80\xc0\xf0\x1f\x9f\x0cK\xfb'),
|
||||
(3, 2, 3, 'saturate'): (b'x\x01\x15\xcbA\x15BQ\x08\x05@\xa2XD\xc5&/\nQ(\xa2\xf2\x9b\x10\xc5\x91\xc3\xc0\xe2B\xc4\xbf'
|
||||
b'\xean<"\x92\xa2\x19\x96xF\xdcH\x0eE3,\x91r\x92C\xd1\x0cK\xbc\xe4$\x87\xa2\x19\x96(\xfd'
|
||||
b'\xb6?n(\x9aa\x89\xaf\x7f\x92C\xd1\x0cK\x8c\x9c\xe4P4\xc3\x12\x97\x9c\xe4P4\xc3^?\xc7\x8a;c',
|
||||
b'x\x01\xed\xdd\xd7U\x15\x00\x00\x04Q$\x89HV@r\x90 Q\xb2\x92\xfb\xaf\xea\xd1\x02\x7f'
|
||||
b'\x1c\xce\xdc\xe9`o\x03;4$\x02\x04\x08\x10 @\x80\x00\x01\x02\x04\x08\x10 @\x80\x00\x01'
|
||||
b'\x02\x04\x08\x10 @\x80\x00\x01\x02\x04\x08\x10 @\x80\x00\x01\x02\x04\x08\x10 @\xe0\xd3\t|'
|
||||
b'\x11\x01\x02\x04\x08\x14\x05\x86E\xa0,0\xa2\xb2\xc0\xa8>V`\xac\xddx\xbd\xaf\xed&\xea}k7Y\xef'
|
||||
b'{\xbb\xa9z\xd3\xedf\xea\xcd\xb6\x9b\xab7\xdfn\xa1\xde\x8fv?\xeb-\xb6[\xaa\xb7\xdc\xee'
|
||||
b'W\xbd\x95v\xab\xf5\xd6\xda\xad\xd7\xdbh\xb7YoK\x04\x08\x10 @\x80\x00\x01\x02\x04\x08\x10'
|
||||
b' @\x80\x00\x01\x02\x04\x08\x10 @\x80\x00\x01\x02\x04\x08\x10 @\x80\x00\x01\x02\x04\x08\x10 '
|
||||
b'@\x80\x00\x01\x02\x04\x08\x10 \xf0.\x81\x81\x08\x10\xa8\nl\x8b\x00\x01\x02\x04\x08\x10 @\x80'
|
||||
b'\x00\x01\x02\x04\x08\x10 @\x80\x00\x01\x02\x04\x08\x10 @\x80\x00\x01\x02\x04\x08\x10'
|
||||
b' @\x80\x00\x01\x02\x04\x08\x10\xf8|\x02;"@\x80\x00\x81\xa2\xc0\xae\x08\x94\x05~\xab,\xb0'
|
||||
b'\xa7\x8f\x15\xd8owP\xef\xb0\xdd\x9fzG\xed\x8e\xeb\x9d\xb4;\xadw\xd6\xee\xbc\xde\xdfv\x17'
|
||||
b'\xf5.\xdb]\xd5\xbbnwS\xef\xb6\xdd]\xbd\x7f\xed\xfe\xd7\xbbo\xf7P\xef\xb1\xddS\xbd\xe7v/\xf5^'
|
||||
b'E\x80\x00\x01\x02\x04\x08\x10 @\x80\x00\x01\x02\x04\x08\x10 @\x80\x00\x01\x02\x04\x08\x10 @'
|
||||
b'\x80\x00\x01\x02\x04\x08\x10 @\x80\x00\x01\x02\x04\x08\x10 @\x80\x00\x01\x02\xef\x12'
|
||||
b'\xa8\x1e\x9f\xdbM\x80\xc0\xe0\r\xd9\xf2;{'),
|
||||
(4, 3, 7, 'saturate'): (b'x\x01\x1d\xcd[\xb5\x90!\x10\x80Q"\x18\x81\x08\xbc{C-@\x04"\xd0\xe0\x10\x81\x08D\xe0\xdd\x1bj'
|
||||
b'\x01"\x10\xe1o\xa0\xfb\xc8Z{\xcd\xbc|C\x08\xff\xdf\xdb\x10:\x9b\xf0.\x84H\xa6\xd2\x19'
|
||||
b'L\x16\x9b\xc3\xe5!\xbc\x0f\xe1\r\x91D\xa6Pit\x06\x93\xc5\xe6py\x08\x1f\xf4D\x12\x99'
|
||||
b'B\xa5\xd1\x19L\x16\x9b\xc3\xe5!|\xd4\x13Id\n\x95Fg0Yl\x0e\x97\x87\x90\xf5D\x12\x99B\xa5'
|
||||
b'\xd1\x19L\x16\x9b\xc3\xe5!|\xd2\x13Id\n\x95Fg0Yl\x0e\x97\x87\xf0YO$\x91)T\x1a\x9d\xc1d\xb19'
|
||||
b'\\\x1e\xc2\x17=\x91D\xa6Pit\x06\x93\xc5\xe6p_\xf7\x17}\xe7\xab\xc1&|s\x8bL\xa53\x98,'
|
||||
b'6\x87\xcbC\xf8\xee\x7f"\x89L\xa1\xd2\xe8\x0c&\x8b\xcd\xe1\xf2\x10~\xe8\x89$2\x85J\xa33\x98,6'
|
||||
b"\x87\xcbC\xf8\xa9'\x92\xc8\x14*\x8d\xce`\xb2\xd8\x1c.\x0fa\xeb\x89$2\x85J\xa33\x98,6\x87\xcb"
|
||||
b'C\xf8\xa5\'\x92\xc8\x14*\x8d\xce`\xb2\xd8\x1c.\x0f\xe1\xb7\x9eH"S\xa84:\x83\xc9bs\xb8<\x84'
|
||||
b'?z"\x89L\xa1\xd2\xe8\x0c&\x8b\xcd\xe1\xbe\xee\x7f\xff\x01!\xba\xf7\x9b',
|
||||
b'x\x01\xed\xdd\xd5\x96\x90\x05\x00E\xe1\x01\xe9\x90n\x90\x96F\xbaE\xa4S@\xba\x15\xa4\xbbKX H*'
|
||||
b'\xdd)\xa9\x12Jw*H\x08Jww\x87\xd25\\\xccC\xec5\xf3\xef\xef\r\xce~\x81\x13\x12"\x0bX'
|
||||
b'\xc0\x02\x16\xb0\x80\x05,`\x01\x0bX\xc0\x02\x16\xb0\x80\x05,`\x01\x0bX \xc2\x16\x88$\x0b'
|
||||
b'X\xc0\x02\x16\x08b\x81\xc8\xb2@\x90\x0b|\xa0 \x17\x88"\xb6@\xd4`\x8b\x16t\xd1\x83-Fx\x17\x93'
|
||||
b'\x15\x8b\x16\x9b\x15\x87\x16\x97\xf5!-\x1e+>-\x01+!-\x11+1-\t+)-\x19+9-\x05+%-\x15+5-\r'
|
||||
b'\xeb#ZZV:ZzV\x06ZFV&Zf\xd6\xc7\xb4,\xac\xac\xb4l\xac\xec\xb4\x1c\xac\x9c\xb4\\\xac\xdc\xb4O'
|
||||
b'XyhyY\xf9h\xf9Y\x05h\x05Y\x85h\x85YEhEY\xc5h\xc5Y%h\x9f\xb2J\xd2>c\x95\xa2}\xce*M+\xc3*K+'
|
||||
b'\xc7*O\xab\xc0\xaaH\xab\xc4\xaaL\xab\xc2\xaaJ\xab\xc6\xfa\x82V\x9dU\x83V\x93\xf5%\xad'
|
||||
b'\x16\xab6\xad\x0e\xab.\xad\x1e\xab>\xad\x01\xab!\xad\x11\xab1\xad\t\xab)\xad\x19\xeb+\xda'
|
||||
b'\xd7\xac\xe6\xb4\x16\xacoh-Y\xadh\xadYmhmY\xedh\xedY\x1dh\x1dY\x9dh\x9dY]h]Y\xddh\xddY=h'
|
||||
b'=Y\xbdh\xbdY}h}Y\xfdh\xfdY\xdf\xd2\x06\xb0\x06\xca\x02\x16\xb0\x80\x05,`\x01\x0bX\xc0\x02'
|
||||
b'\x16\xb0\x80\x05,`\x01\x0bX\xc0\x02\x16\xb0\x80\x05,`\x01\x0bX\xc0\x02\x16\xb0\x80\x05\xc2\n'
|
||||
b'\x84\xca\x02\x16\x08j\x81A\xb2\x80\x05,`\x01\x0bX\xc0\x02\x16\xb0\x80\x05,`\x01\x0bX\xc0'
|
||||
b'\x02\x16\xb0\x80\x05"n\x81\xc1\xb2\x80\x05,`\x81 \x16\xf8N\x16\x08r\x81!\nr\x81\xa1'
|
||||
b'b\x0b|\x1fl\xc3\x82nx\xb0\x8d\x08\xefF\xb2F\xd1F\xb3~\xa0\xfd\xc8\x1aC\x1b\xcb\x1a'
|
||||
b'G\x1b\xcf\x9a@\x9b\xc8\x9aD\x9b\xcc\x9aB\x9b\xca\x9aF\x9b\xce\x9aA\x9b\xc9\x9aE\x9b\xcd\x9a'
|
||||
b'C\x9b\xcb\xfa\x896\x8f5\x9f\xb6\x80\xb5\x90\xb6\x88\xb5\x98\xf63\xeb\x17\xda\xaf\xac'
|
||||
b'%\xb4\xa5\xace\xb4\xe5\xac\xdfh\xbf\xb3V\xd0V\xb2V\xd1V\xb3\xd6\xd0\xd6\xb2\xd6\xd1\xd6\xb3'
|
||||
b'6\xd06\xb26\xd16\xb3\xb6\xd0\xb6\xb2\xb6\xd1\xb6\xb3v\xd0v\xb2\xfe\xa0\xfd\xc9\xdaE\xdb\xcd'
|
||||
b'\xfa\x8b\xb6\x87\xb5\x97\xb6\x8f\xb5\x9f\xf67\xeb\x00\xed \xeb\x1f\xda\xbf\xacC\xb4\xc3'
|
||||
b'\xac#\xb4\xa3\xacc\xb4\xe3\xac\x13\xb4\x93\xacS\xb4\xd3\xac3\xb4\xb3\xacs\xb4\xf3'
|
||||
b'\xac\x0b\xb4\x8b\xacK\xb4\xcb\xac+\xb4\xab\xack\xb4\xeb\xac\x1b\xb4\x9b\xac[\xb4\xdb'
|
||||
b'\xac;\xb4\xbb\xac{\xb4\xfb\xac\x07\xb4\x87\xacG\xb4\xc7\xac\xffh\xff\xb3\x9e\xd0\x9e'
|
||||
b'\xb2\x9e\xd1\x9e\xb3^\xd0^\xb2^\xd1^\xb3\xde\xd0\xde\xb2\xde\xc9\x02\x16\xb0\x80\x05'
|
||||
b',`\x01\x0bX\xc0\x02\x16\xb0\x80\x05,`\x01\x0bX\xc0\x02\x16\xb0\x80\x05,`\x01\x0bX\xc0'
|
||||
b'\x02\x16\xb0\x80\x05\xc2\n\x04\xf5\xf8\xdc\xdd\x16\xb0@\xe8{\t?\xc8\x90'),
|
||||
(5, 2, 15, 'saturate'): (b'x\x01M\xcb\xd9\x11\x10T\x0c@\xd1\x94B\x17 \x08\x06Dh#\x9d\x90R\xd2\x05\xca\x1a\x17'
|
||||
b'\xa0\x8dt\x02\xc7\x1f\xc7\xcc\x9c\xf7\xf1n\x12\xf1\xef\xf4C\xcf\xa3\x88\xa4\x19\x96#~\x8ax@'
|
||||
b"R4\xc3r\xc4c\x9d\xa4h\x86\xe5\x88':I\xd1\x0c\xcb\x11?\xeb$E3,G<\xd5I\x8afX\x8ex\xa6\x93"
|
||||
b'\x14\xcd\xb0\x1c\xf1\x8bNR4\xc3rD\xea$E3,G<\xd7I\x8afX\x8ex\xa1\x93\x14\xcd\xb0\x1c'
|
||||
b'\xf1\xabNR4\xc3r\xc4K\x9d\xa4h\x86\xe5\x88\xdft\x92\xa2\x19\x96#^\xe9$E3,G\xbc\xd6I'
|
||||
b'\x8a~\xa3\xfdO\xb4\xbf\xb7\xf6~\xb7C3,G\xfc\xe1\x9e\xa4h\x86\xe5\x88w:I\xd1\x0c'
|
||||
b'\xcb\x11\xefu\x92\xa2\x19\x96#>\xe8$E3,G|\xd4I\x8afX\x8e\xf8\xa4\x93\x14\xcd\xb0\x1c\xf1Y'
|
||||
b"')\x9aa9bu\x92\xa2\x19\x96#\xfe\xd4I\x8afX\x8e\xf8K')\x9aa9\xe2o\x9d\xa4h\x86\xe5\x88\x7ft"
|
||||
b'\x92\xa2\x19\x96#\xbe\xe8$E3,G|\xd5I\x8afX\x8e\xf8\xa6\x93\x14\xfd]\xfb\xcf\x0f'
|
||||
b'\xd2\x15\xf0\xcf',
|
||||
b'x\x01\xed\xddC\xa2\x1c\x00\x00D\xc1\xd8\xb6m\xdb\xb6m\xdb66\xb1m\xdb\xb6m\xdb\xb6m'
|
||||
b',\x92c\xfcEM\xdd\xa0\xdf\x05:X\xb0 \x16\xdc\x16B\x17\xd2\x16J\x17\xda\x16F\x17\xd6'
|
||||
b'\x16N\x17\xde\x16A\x17\xd1\x16I\x17\xd9\x16E\x17\xd5\x16M\x17\xdd\x16C\x17\xd3\x16K\x17\xdb'
|
||||
b'\x16G\x17\xd7\x16O\x17\xdf\x96@\x97\xd0\x96H\x97\xd8\x96D\x97\xd4\x96L\x97\xdc\x96B\x97\xd2'
|
||||
b'\x96J\x97\xda\x96F\x97\xd6\x96N\x97\xde\x96A\x97\xd1\x96I\x97\xd9\x96E\x97\xd5\x96M\x97\xdd'
|
||||
b'\x96C\x97\xd3\x96K\x97\xdb\x96G\x97\xd7\x96O\x97\xdfV@W\xd0VHW\xd8VDW\xd4VLW\xdcVBW\xd2'
|
||||
b'VJW\xdaVFW\xd6VNW\xdeVAW\xd1VIW\xd9VEW\xd5VMW\xddVCW\xd3VKW\xdbVGW\xd7VOW\xdf\xd6@\xd7\xd0'
|
||||
b'\xd6H\xd7\xd8\xd6D\xd7\xd4\xd6L\xd7\xdc\xd6B\xd7\xd2\xd6J\xd7\xda\xd6F\xd7\xd6\xd6N\xd7\xde'
|
||||
b'\xd6A\xd7\xd1\xd6I\xd7\xd9\xd6E\xd7\xd5\xd6M\xd7\xdd\xd6C\xd7\xd3\xd6K\xd7\xdb\xd6G\xd7\xd7'
|
||||
b'\xd6O\xd7\xdf6@70 H\x0b\xfc\x0b\x08\x14\x08\x14P\x0b\x0c\nj\x83mCtCm\xc3t\xc3m#t#m'
|
||||
b'\xa3t\xa3mctcm\xe3t\xe3m\x13t\x13m\x93t\x93mStSm\xd3t\xd3m3t3m\xb3t\xb3mstsm\xf3t\xf3m'
|
||||
b'\x0bt\x0bm\x8bt\x8bmKtKm\xcbt\xcbm+t+m\xabt\xabmktkm\xebt\xebm\x1bt\x1bm\x9bt\x9bm[t[m'
|
||||
b"\xdbt\xdbm;t;m\xbbt\xbbm{t{m\xfbt\xfbm\x07t\x07m\x87t\x87mGtGm\xc7t\xc7m't'm\xa7t\xa7mgtgm"
|
||||
b'\xe7t\xe7m\x17t\x17m\x97t\x97mWtWm\xd7t\xd7m7t7m\xb7t\xb7mwtwm\xf7t\xf7m\x0ft\x0fm'
|
||||
b'\x8ft\x8fmOtOm\xcft\xcfm/t/m\xaft\xafmotom\xeft\xefm\x1ft\x1fm\x9ft\x9fm_t_m\xdft\xdfm?t?m'
|
||||
b'\xbft\xbfm\x7ft\x7f\x03\x82\xb4\x80z|\x1e\xd8\x1d(\x10(\xf0\xef?\xe6\xfc\r\x9b'),
|
||||
(4, 3, 7, 'overflow'): (b'x\x01\x1d\xcd[\xb5\x90!\x10\x80Q"\x18\x81\x08\xbc{C-@\x04"\xd0\xe0\x10\x81\x08D\xe0\xdd\x1bj'
|
||||
b'\x01"\x10\xe1o\xa0\xfb\xc8Z{\xcd\xbc|C\x08\xff\xdf\xdb\x10:\x9b\xf0.\x84H\xa6\xd2\x19'
|
||||
b'L\x16\x9b\xc3\xe5!\xbc\x0f\xe1\r\x91D\xa6Pit\x06\x93\xc5\xe6py\x08\x1f\xf4D\x12\x99'
|
||||
b'B\xa5\xd1\x19L\x16\x9b\xc3\xe5!|\xd4\x13Id\n\x95Fg0Yl\x0e\x97\x87\x90\xf5D\x12\x99B\xa5'
|
||||
b'\xd1\x19L\x16\x9b\xc3\xe5!|\xd2\x13Id\n\x95Fg0Yl\x0e\x97\x87\xf0YO$\x91)T\x1a\x9d\xc1d\xb19'
|
||||
b'\\\x1e\xc2\x17=\x91D\xa6Pit\x06\x93\xc5\xe6p_\xf7\x17}\xe7\xab\xc1&|s\x8bL\xa53\x98,'
|
||||
b'6\x87\xcbC\xf8\xee\x7f"\x89L\xa1\xd2\xe8\x0c&\x8b\xcd\xe1\xf2\x10~\xe8\x89$2\x85J\xa33\x98,6'
|
||||
b"\x87\xcbC\xf8\xa9'\x92\xc8\x14*\x8d\xce`\xb2\xd8\x1c.\x0fa\xeb\x89$2\x85J\xa33\x98,6\x87\xcb"
|
||||
b'C\xf8\xa5\'\x92\xc8\x14*\x8d\xce`\xb2\xd8\x1c.\x0f\xe1\xb7\x9eH"S\xa84:\x83\xc9bs\xb8<\x84'
|
||||
b'?z"\x89L\xa1\xd2\xe8\x0c&\x8b\xcd\xe1\xbe\xee\x7f\xff\x01!\xba\xf7\x9b',
|
||||
b'x\x01\xed\xdc\x05\xb2\x90\x05\x00E\xe1\x87\x80\xa4t\x83tw\x97Jw\x0bHKw\x83\x80\x92Cww7'
|
||||
b'\x92\xd2\x1dRJww\x97\xa4\x92J\xba\x8c3\x8f\xff|\x1b\xb83g\x017$D\x16\xb0\x80\x05,`\x01'
|
||||
b'\x0bX\xc0\x02\x16\xb0\x80\x05,`\x01\x0bX\xc0\x02\x16\xb0\xc0g[ \x8c,`\x01\x0bX '
|
||||
b'\x88\x05\xbe\x90\x05\x82\\ \xac\x82\\ \x9c\xd8\x02\xe1\x83\xed\xcb\xa0\x8b\x10l\x11'
|
||||
b'C\xbbH\xac\xc8\xb4(\xac\xa8\xb4\xafX\xd1h\xd1Y1h1Y\xb1h\xb1YqhqY\xf1h\xf1Y\th\tY\x89h\x89Y'
|
||||
b'Ih_\xb3\x92\xd2\x92\xb1\x92\xd3R\xb0R\xd2R\xb1R\xd3\xd2\xb0\xd2\xd2\xd2\xb1\xd2\xd32\xb0'
|
||||
b'2\xd22\xb12\xd3\xb2\xb0\xb2\xd2\xb2\xb1\xb2\xd3r\xb0r\xd2r\xb1r\xd3\xf2\xb0\xf2\xd2\xf2\xb1'
|
||||
b'\xf2\xd3\n\xb0\n\xd2\xbea}K\xfb\x8eU\x88V\x98U\x84V\x94U\x8cV\x9cU\x82V\x92U\x8aV\x9a'
|
||||
b'U\x86V\x96U\x8eV\x9eU\x81V\x91U\x89V\x99U\x85\xf6=\xab*\xad\x1a\xab:\xed\x07V\rZMV-ZmV\x1dZ]'
|
||||
b'V=\xda\x8f\xac\xfa\xb4\x06\xac\x86\xb4F\xac\xc6\xb4&\xac\xa6\xb4f\xac\xe6\xb4\x16'
|
||||
b'\xac\x96\xb4V\xac\xd6\xb46\xac\xb6\xb4v\xac\xf6\xb4\x0e\xac\x8e\xb4N\xac\xce\xb4\x9fX]h]'
|
||||
b'Y\xddh?\xb3~\xa1ug\xf5\xa0\xf5d\xf5\xa2\xf5f\xf5\xa1}\x92\x05,`\x01\x0bX\xc0\x02\x16\xb0\x80'
|
||||
b'\x05,`\x01\x0bX\xc0\x02\x16\xb0\x80\x05,`\x01\x0bX\xc0\x02\x16\xb0\x80\x05,`\x01\x0bX'
|
||||
b'\xc0\x02\x16`\x0b\xf4\x95\x05,`\x01\x0bX\xc0\x02\x16\xb0\x80\x05,`\x01\x0bX\xc0\x02\x16\xb0'
|
||||
b"\x80\x05,\xf0\xf9\x16\xe8'\x0bX\xc0\x02\x16\x08b\x81\xfe\xb2@\x90\x0b\x0cP\x90\x0b\x0c\x14["
|
||||
b'`P\xb0\r\x0e\xba!\xc164\xb4\x1b\xc6\x1aN\x1b\xc1\x1aI\x1b\xc5\x1aM\x1b\xc3\x1aK\x1b'
|
||||
b'\xc7\x1aO\x9b\xc0\x9aH\x9b\xc4\x9aL\x9b\xc2\x9aJ\x9b\xc6\x9aN\x9b\xc1\x9aI\x9b\xc5\x9aM\x9b'
|
||||
b'\xc3\x9aK\x9b\xc7\x9aO[\xc0ZH[\xc4\xfa\x95\xb6\x98\xb5\x84\xb6\x94\xb5\x8c\xb6'
|
||||
b'\x9c\xb5\x82\xf6\x1bk%m\x15k5m\rk-m\x1dk=m\x03k#m\x13k3m\x0bk+m\x1bk;m\x07\xebw\xda'
|
||||
b"N\xd6.\xdan\xd6\x1e\xda^\xd6\x1f\xb4?Y\xfbh\xfbY\x07h\x07Y\x87h\x87YGhGY\xc7h\xc7Y'h'Y\xa7h"
|
||||
b'\xa7YghgY\xe7h\xe7Y\x17h\x17Y\x97h\x97YWhWY\xd7h\xd7Y7h7Y\xb7h\xb7YwhwY\xf7h\xf7Y\x0fh'
|
||||
b'\x7f\xb1\x1e\xd2\x1e\xb1\x1e\xd3\x9e\xb0\x9e\xd2\x9e\xb1\xfe\xa6\xfd\xc3zN{\xc1zI{\xc5zM'
|
||||
b'{\xc3\xfa\x97\xf6\x1f\xeb-\xed\x1d\xeb=\xed\x03\xeb#\x8d\xbd\xefw\xdd\x02\x16\xb0\x80\x05,`'
|
||||
b'\x01\x0bX\xc0\x02\x16\xb0\x80\x05,`\x01\x0bX\xc0\x02\x16\xb0\x80\x05,`\x01\x0bX\xc0\x02\x16'
|
||||
b'\xb0\x80\x05,`\x01\x0b|\xfa\x1f\xb2\xf6b\xf1'),
|
||||
(5, 2, 15, 'overflow'): (b'x\x01M\xcb\xd9\x11\x10T\x0c@\xd1\x94B\x17 \x08\x06Dh#\x9d\x90R\xd2\x05\xca\x1a\x17'
|
||||
b'\xa0\x8dt\x02\xc7\x1f\xc7\xcc\x9c\xf7\xf1n\x12\xf1\xef\xf4C\xcf\xa3\x88\xa4\x19\x96#~\x8ax@'
|
||||
b"R4\xc3r\xc4c\x9d\xa4h\x86\xe5\x88':I\xd1\x0c\xcb\x11?\xeb$E3,G<\xd5I\x8afX\x8ex\xa6\x93"
|
||||
b'\x14\xcd\xb0\x1c\xf1\x8bNR4\xc3rD\xea$E3,G<\xd7I\x8afX\x8ex\xa1\x93\x14\xcd\xb0\x1c'
|
||||
b'\xf1\xabNR4\xc3r\xc4K\x9d\xa4h\x86\xe5\x88\xdft\x92\xa2\x19\x96#^\xe9$E3,G\xbc\xd6I'
|
||||
b'\x8a~\xa3\xfdO\xb4\xbf\xb7\xf6~\xb7C3,G\xfc\xe1\x9e\xa4h\x86\xe5\x88w:I\xd1\x0c'
|
||||
b'\xcb\x11\xefu\x92\xa2\x19\x96#>\xe8$E3,G|\xd4I\x8afX\x8e\xf8\xa4\x93\x14\xcd\xb0\x1c\xf1Y'
|
||||
b"')\x9aa9bu\x92\xa2\x19\x96#\xfe\xd4I\x8afX\x8e\xf8K')\x9aa9\xe2o\x9d\xa4h\x86\xe5\x88\x7ft"
|
||||
b'\x92\xa2\x19\x96#\xbe\xe8$E3,G|\xd5I\x8afX\x8e\xf8\xa6\x93\x14\xfd]\xfb\xcf\x0f'
|
||||
b'\xd2\x15\xf0\xcf',
|
||||
b'x\x01\xed\xddS\xb6\x1e\x06\x00\x85\xd1\x1b\xdb\xb6\x1b\xdb\xb6\x9b\x06\x8dm\xdbz\x88'
|
||||
b"m\xabI\x1b\xa3\xb1m\xdb\xb6mg\xad\x9ba\xdc\x87\xfd\xef\x19\x9co\x02'((\x84\x85\xb2"
|
||||
b'\x85\xd6\x85\xb1\x85\xd5\x85\xb3\x85\xd7E\xb0E\xd4E\xb2E\xd6E\xb1E\xd5E\xb3E\xd7\xc5\xb0'
|
||||
b'\xc5\xd4\xc5\xb2\xc5\xd6\xc5\xb1\xc5\xd5\xc5\xb3\xc5\xd7%\xb0%\xd4%\xb2%\xd6%\xb1%\xd5%\xb3'
|
||||
b'%\xd7\xa5\xb0\xa5\xd4\xa5\xb2\xa5\xd6\xa5\xb1\xa5\xd5\xa5\xb3\xfd\xa1Ko\xcb\xa0\xcbh'
|
||||
b'\xcb\xa4\xcbl\xcb\xa2\xcbj\xcb\xa6\xcbn\xcb\xa1\xcbi\xcb\xa5\xcbm\xcb\xa3\xcbk\xcb\xa7\xcbo'
|
||||
b'+\xa0+h+\xa4+l+\xa2+j+\xa6+n+\xa1+i+\xa5+m+\xa3+k+\xa7+o\xab\xa0\xabh\xab\xa4\xabl'
|
||||
b'\xfbSW\xc5\xf6\x97\xae\xaa\xad\x9a\xae\xba\xad\x86\xeeo[M]-[m]\x1d[]]=[}]\x03[C]#[c]\x13'
|
||||
b"[S]3[s]\x0b[K]+[k]\x1b[[];[{]\x07[G]'[g]\x17[W]7[w]\x0f[O]/[o]\x1f[_]?[\x7f\xdd\x00"
|
||||
b'\xdb\xc0\x90\x16\x1c\x10(\x10(\xa0\x16\x18\x14\xd2\x06\xdb\x86\xe8\x86\xda\x86\xe9\x86\xdb'
|
||||
b'F\xe8F\xdaF\xe9F\xdb\xc6\xe8\xc6\xda\xc6\xe9\xc6\xdb&\xe8&\xda&\xe9&\xdb\xa6\xe8\xa6\xda'
|
||||
b'\xa6\xe9\xa6\xdbf\xe8f\xdaf\xe9f\xdb\xe6\xe8\xe6\xda\xe6\xe9\xfe\xb1\xcd\xd7-\xb0'
|
||||
b'\xfd\xab\xfb\xcf\xb6P\xb7\xc8\xb6X\xb7\xc4\xb6T\xb7\xcc\xb6\\\xb7\xc2\xb6R\xb7\xca'
|
||||
b'\xf6\xbfn\xb5m\x8dn\xadm\x9dn\xbdm\x83n\xa3m\x93n\xb3m\x8bn\xabm\x9bn\xbbm\x87n\xa7'
|
||||
b'm\x97n\xb7m\x8fn\xafm\x9fn\xbf\xed\x80\xee\xa0\xed\x90\xee\xb0\xed\x88\xee\xa8'
|
||||
b'\xed\x98\xee\xb8\xed\x84\xee\xa4\xed\x94\xee\xb4\xed\x8c\xee\xac\xed\x9c\xee\xbc'
|
||||
b'\xed\x82\xee\xa2\xed\x92\xee\xb2\xed\x8a\xee\xaa\xed\x9a\xee\xba\xed\x86\xee\xa6'
|
||||
b'\xed\x96\xee\xb6\xed\x8e\xee\xae\xed\x9e\xee\xbe\xed\x81\xee\xa1\xed\x91\xee\xb1'
|
||||
b'\xed\x89\xee\xa9\xed\x99\xee\xb9\xed\x85\xee\xa5\xed\x95\xee\xb5\xed\x8d\xee\xad'
|
||||
b'\xed\x9d\xee\xbd\xed\x83\xee\xa3\xed\x93\xee\xb3\xed\x8b\xee\xab\xed\x9b\xee\xbb'
|
||||
b'\xed\x87\xee\xa7\xedWHS\x8f\xcf\x03\xbb\x03\x05\x02\x05\x82\x7f\x03\xb3\x87\x0e\x9d')}
|
||||
|
||||
|
||||
binary8_luts_compressed = \
|
||||
{(4, 8): (b'x\x01\x15\xcb[\xd5P!\x10\x80Q"\x18\x81\x08<{E-@\x04"\xd0@"\x10\x81\x08<{E\xff\x02\'\x02\x11h\xa0\xdbY'
|
||||
b'k\xcf\xcb\xcc\x17\xc2\xff\xe9\xaf\xad7!d:\x93\xcd!\xbc\r\xe1\x15\x91D\xa6Pit\x06\x93\xc5\xe6\xe1p\t\xef\xf4'
|
||||
b'D\x12\x99B\xa5\xd1\x19L\x16\x9b\x87\xc3%\xbc\xd7\x13Id\n\x95Fg0Yl\x1e\x0e\x97\xf0AO$\x91)T\x1a\x9d\xc1d\xb1'
|
||||
b"y8\\B\xd6\x13Id\n\x95Fg0Yl\x1e\x0e\x97\xf0QO$\x91)T\x1a\x9d\xc1d\xb1y8\\\xc2'=\x91D\xa6Pit\x06\x93"
|
||||
b'\xc5\xe6\xe1p\t\x9f\xf5D\x12\x99B\xa5\x7f\xf1O\xff\xea\xef\x9b\x1b\x9d\xc9\xe6\x10\xbe\xeb\x89$2\x85J\xa3'
|
||||
b'3\x98,6\x0f\x87K\xf8\xa1\'\x92\xc8\x14*\x8d\xce`\xb2\xd8<\x1c.\xe1\xa7\x9eH"S\xa84:\x83\xc9b\xf3p\xb8\x84_z'
|
||||
b'"\x89L\xa1\xd2\xe8\x0c&\x8b\xcd\xc3\xe1\x12\xb6\x9eH"S\xa84:\x83\xc9b\xf3p\xb8\x84\xdfz"\x89L\xa1\xd2\xe8'
|
||||
b'\x0c&\x8b\xcd\xc3\xe1\x12\xfe\xe8\x89$2\x85J\xa33\x98,6\x0f\x87Kx\xd1\x13Id\n\x95\xfe\xf7\x1f[)\xf3`',
|
||||
b'x\x01\xed\xdd\x05\xba\x96\x05\x00\x05\xe1\x9f\xee\x06\xe9FZA\xa4\xbb\xbb;\xa4SB\xba\xeb\xd2\xdd\x8dt\x97'
|
||||
b'\x92J(\xa14\xa2\x84\x92\x8a\xa4\x82\xd2\x1d\x12.c\x9e\xcb7\xef\x0e\xcel\xe0\x84B\xb2\x80\x05,`\x01'
|
||||
b'\x0bX\xc0\x02\x16\xb0\x80\x05,`\x01\x0bX\xe0\xbd/\x10A\x16\xb0\x80\x05,\x10\xc4\x02\x11e\x81 \x17\x88'
|
||||
b'\xa4 \x17\x88,\xb6@\x94`\x8b\x1at\xd1\x82-zx\x17\x83\x15\x93\x16\x8b\x15\x9b\x16\x87\x15\x97\x16\x8f\x15'
|
||||
b'\x9f\x96\x80\x95\x90\x96\x88\x95\x98\x96\x84\xf5\x01-)+\x19-9+\x05-%+\x15-5+\r--+\x1d-=+\x03-#+\x13\xedCV'
|
||||
b'fZ\x16VVZ6VvZ\x0eVN\xdaG\xac\x8fi\xb9X\xb9i\x9f\xb0\xf2\xd0>e\xe5\xa5\xe5c\xe5\xa7\x15`\x15\xa4\x15b'
|
||||
b'\x15\xa6\x15a\x15\xa5\x15c\x15\xa7\x95`\x95\xa4\x95b\x95\xa6\x95a\x95\xa5\x95c\x95\xa7U`U\xa4UbU\xa6Ua'
|
||||
b'U\xa5UcU\xa7\xd5`\xd5\xa4\xd5b\xd5\xa6\xd5a\xd5\xa5\xd5c\xd5\xa75`5\xa45b5\xa65a}Fk\xcajFk\xcejAk\xc9'
|
||||
b"jEk\xcdjCk\xcbjGk\xcf\xea@\xfb\x9c\xd5\x91\xd6\x89\xd5\x99\xd6\x85\xf5\x05\xad+\xab\x1b\xad;\xab\x07\xad'"
|
||||
b'\xab\x17\xad7\xab\x0f\xad/\xab\x1f\xad?k\x00m k\x10m0k\x08m\xa8,`\x01\x0bX\xc0\x02\x16\xb0\x80\x05,'
|
||||
b'`\x01\x0bX\xc0\x02\x16\xb0\x80\x05,`\x01\x0bX\xc0\x02\x16\xb0\x80\x05,`\x01\x0bX\xc0\x02\x16\xb0\x00\\'
|
||||
b' L\x16\xb0@P\x0b\xbc\xf7\xff\x86\x0e\xb4\x80\x05,`\x01\x0bX\xc0\x02\x16\xb0\x80\x05,`\x01\x0bX '
|
||||
b'\x14\x1a&\x0bX\xc0\x02\x16\x08b\x81\xe1\xb2@\x90\x0b\x8cP\x90\x0b\x8c\x14[`T\xb0\x8d\x0e\xba1\xc16'
|
||||
b'6\xbc\x1b\xc7\x1aO\x9b\xc0\x9aH\x9b\xc4\x9aL\x9b\xc2\x9aJ\x9b\xc6\x9aN\x9b\xc1\x9aI\x9b\xc5\x9aM\x9b\xc3'
|
||||
b'\x9aK\x9b\xc7\x9aO\xfb\x92\xb5\x80\xb6\x90\xb5\x88\xb6\x98\xb5\x84\xb6\x94\xb5\x8c\xb6\x9c\xb5\x82\xb6\x92'
|
||||
b'\xb5\x8a\xb6\x9a\xb5\x86\xb6\x96\xb5\x8e\xb6\x9e\xb5\x81\xf6\x15\xebk\xdaF\xd6&\xdaf\xd6\x16\xdaV\xd66\xda7'
|
||||
b'\xacoi\xdbY;h;Y\xbbh\xdf\xb1\xbe\xa7\xedf\xed\xa1\xede\xed\xa3\xfd\xc0\xfa\x91\xb6\x9fu\x80v\x90u\x88v'
|
||||
b'\x98u\x84v\x94u\x8c\xf6\x13\xeb8\xedg\xd6/\xb4\x13\xac\x93\xb4S\xac\xd3\xb4_Y\xbf\xd1\xce\xb0\xce\xd2'
|
||||
b'\xce\xb1\xce\xd3.\xb0.\xd2~g\xfdA\xbb\xc4\xfa\x93v\x99u\x85v\x95u\x8dv\x9du\x83\xf6\x17\xebo\xdaM\xd6-'
|
||||
b'\xda?\xac\x7fi\xb7YwhwY\xf7h\xf7Y\x0fh\x0fY\x8fh\x8fYOhOY\xcfh\xcfY/h/Y\xafh\xff\xb1^\xd3\xde\xb0\xde'
|
||||
b'\xd2\xde\xc9\x02\x16\xb0\x80\x05,`\x01\x0bX\xc0\x02\x16\xb0\x80\x05,`\x01\x0bX\xc0\x02\x16\xb0\x80\x05,`'
|
||||
b'\x01\x0bX\xc0\x02\x16\xb0\x80\x05,`\x01\x0b\xc0\x05\x82z|\xeen\x0bX \xec\x7f\xc6\xe4\x02%'),
|
||||
(5, 16): (b'x\x01\x1d\xc9\x89\r\x10\x04\x0c\x86\xd1\x8e\xc2\x16 \x97\x059\xd6\xe8&t\x94n\xa1\\RQ`\x8dn\x02/6yI'
|
||||
b"\xf3\x7f\x11\xff\xdf\xc3\x88f\x89G\x11\x0fH\x8afX\x8e\xf8M')\x9aa9\xe2\xb1NR4\xc3r\xc4\x13\x9d\xa4h"
|
||||
b'\x86\xe5\x88\xa7:I\xd1\x0c\xcb\x11\xcft\x92\xa2\x19\x96#\x9e\xeb$E3,G\xfc\xae\x93\x14\xcd\xb0\x1c\x91'
|
||||
b":I\xd1\x0c\xcb\x11/t\x92\xa2\x19\x96#^\xea$E3,G\xfc\xa1\x93\x14\xcd\xb0\x1c\xf1J')\x9aa9\xe2\xb5NR4\xc3"
|
||||
b'r\xc4\x1b\x9d\xa4h\x86\xe5\x88\xb7:I\xbf\xb3\x13\x7f\xfaY\xe2/\x9d\xa4h\x86\xe5\x88\xf7:I\xd1\x0c\xcb'
|
||||
b'\x11\x1ft\x92\xa2\x19\x96#>\xea$E3,G|\xd2I\x8afX\x8e\xf8\xac\x93\x14\xcd\xb0\x1c\xf1\xb7NR4\xc3r'
|
||||
b"\xc4\x17\x9d\xa4h\x86\xe5\x88\xd5I\x8afX\x8e\xf8G')\x9aa9\xe2\xabNR4\xc3r\xc4\xbf:I\xd1\x0c\xcb\x11"
|
||||
b"\xff\xe9$E3,G|\xd3I\x8afX\x8e\xf8\xae\x93\x14\xcd\xb0\x1c\xf1C'\xe9\x9f\xbf\x00Gi\xed\x02",
|
||||
b'x\x01\xed\xddU\xd6\x96\x05\x00\x85\xd1\x9f\x16\xa4\x14\x04\xe9P\xa4K\x1a\xe9\x14\x10\t\xe9\x0eQ'
|
||||
b'\xba\xc1\xa0\xbb\xbb\xa5\x1b)%\xa5;%\xa5\xbb;%\x94\x8eAp\xf1.\xd6\xfe\xf6\x0c\xce3\x81\x13\x16\xf6'
|
||||
b'\x8e\xc2\x05+|\xd0"\x04+b\xd0"\x05+r\xd0\xa2\x04\xeb\x83\xf7]T[4\xdd\x87\xb6\xe8\xba\x18\xb6\x98\xbaX\xb6'
|
||||
b'\xd8\xba\x8fl\x1f\xeb\xe2\xd8\xe2\xea>\xb1\xc5\xd3\xc5\xb7}\xaaK`K\xa8KdK\xacKbK\xaaKfK\xaeKaK\xa9\xfb\xcc'
|
||||
b'\xf6\xb9.\x95\xed\x0b]j[\x1a]Z[:]z[\x06]F[&]f[\x16]V\xdb\x97\xbal\xb6\xec\xba\x1c\xb6\x9c\xba\\'
|
||||
b'\xb6\xdc\xba<\xb6\xbc\xba\xafl\xf9t\xf9m\x05t\x05m\x85t\x85mEtEm\xc5t\xc5m%t%m_\xebJ\xd9J\xeb\xca'
|
||||
b'\xd8\xbe\xd1\x95\xb5}\xab+g+\xaf\xab`\xab\xa8\xfb\xceVIW\xd9VEW\xd5VMW\xddVCW\xd3VKW\xdbVGW\xd7VOW'
|
||||
b'\xdf\xd6@\xd7\xd0\xf6\xbd\xae\x91\xed\x07\xdd\x8f\xb6\xc6\xba&\xb6\xa6\xbaf\xb6\xe6\xba\x16\xb6\x96\xba'
|
||||
b'V\xb6\xd6\xba6\xb6\xb6\xbav\xb6\xf6\xba\x0e\xb6\x8e\xba\x9fl?\xeb~\xb1\xfd\xaa\xebd\xeb\xac\xebb\xeb\xaa'
|
||||
b"\xeb\x16\x12h\x81\xee!\xa1\x02\xa1\x02j\x81\xb0w\xd5#X=\x83\xd6+X\xbd\x83\xd6'X}\x83\xd6/X\xfd\xdfw"
|
||||
b'\x03l\x03u\x83l\x83uClCu\xc3l\xc3u#l#u\xa3l\xa3uclcu\xe3l\xe3u\x13l\xbf\xe9&\xda&\xe9&\xdb\xa6\xe8'
|
||||
b'\xa6\xda\xa6\xe9\xa6\xdbf\xe8f\xdaf\xe9f\xdb\xe6\xe8\xe6\xda\xe6\xe9~\xb7\xcd\xd7-\xb0-\xd4-\xb2-\xd6'
|
||||
b'\xfda\xfbS\xb7\xc4\xb6T\xb7\xcc\xb6\\\xb7\xc2\xb6R\xf7\x97m\x95n\xb5m\x8dn\xadm\x9dn\xbdm\x83n\xa3m\x93'
|
||||
b'n\xb3m\x8bn\xabm\x9bn\xbbm\x87n\xa7m\x97n\xb7\xedo\xdd\x1e\xdb^\xdd>\xdb~\xdd\x01\xdbA\xdd?\xb6C'
|
||||
b'\xba\xc3\xb6#\xba\xa3\xb6c\xba\xe3\xb6\x13\xba\x93\xb6S\xba\xd3\xb63\xba\xb3\xb6s\xba\xf3\xb6\x0b'
|
||||
b'\xba\x8b\xb6K\xba\xcb\xb6+\xba\xab\xb6k\xba\xeb\xb6\x1b\xba\x9b\xb6[\xba\xdb\xb6;\xba\xbb\xb6{'
|
||||
b'\xba\xfb\xb6\x7fu\x0fl\x0fu\x8fl\x8fu\xff\xd9\xfe\xd7=\xb1=\xd5=\xb3=\xd7\xbd\xb0\xbd\xd4\xbd\xb2\xbd'
|
||||
b'\xd6\xbd\t\t\xb4\x80z|\x1e\xda\x1d*\x10*\xd0\xfd-\x8c\x93\xc6\x0e')}
|
||||
95
code/.venv/lib/python3.12/site-packages/bitstring/methods.py
Normal file
95
code/.venv/lib/python3.12/site-packages/bitstring/methods.py
Normal file
@@ -0,0 +1,95 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import bitstring
|
||||
from bitstring.bitstream import BitStream
|
||||
from bitstring.utils import tokenparser
|
||||
from bitstring.exceptions import CreationError
|
||||
from typing import Union, List
|
||||
from bitstring.bitstore import BitStore
|
||||
from bitstring.bitstore_helpers import bitstore_from_token
|
||||
|
||||
|
||||
def pack(fmt: Union[str, List[str]], *values, **kwargs) -> BitStream:
|
||||
"""Pack the values according to the format string and return a new BitStream.
|
||||
|
||||
fmt -- A single string or a list of strings with comma separated tokens
|
||||
describing how to create the BitStream.
|
||||
values -- Zero or more values to pack according to the format.
|
||||
kwargs -- A dictionary or keyword-value pairs - the keywords used in the
|
||||
format string will be replaced with their given value.
|
||||
|
||||
Token examples: 'int:12' : 12 bits as a signed integer
|
||||
'uint:8' : 8 bits as an unsigned integer
|
||||
'float:64' : 8 bytes as a big-endian float
|
||||
'intbe:16' : 2 bytes as a big-endian signed integer
|
||||
'uintbe:16' : 2 bytes as a big-endian unsigned integer
|
||||
'intle:32' : 4 bytes as a little-endian signed integer
|
||||
'uintle:32' : 4 bytes as a little-endian unsigned integer
|
||||
'floatle:64': 8 bytes as a little-endian float
|
||||
'intne:24' : 3 bytes as a native-endian signed integer
|
||||
'uintne:24' : 3 bytes as a native-endian unsigned integer
|
||||
'floatne:32': 4 bytes as a native-endian float
|
||||
'hex:80' : 80 bits as a hex string
|
||||
'oct:9' : 9 bits as an octal string
|
||||
'bin:1' : single bit binary string
|
||||
'ue' / 'uie': next bits as unsigned exp-Golomb code
|
||||
'se' / 'sie': next bits as signed exp-Golomb code
|
||||
'bits:5' : 5 bits as a bitstring object
|
||||
'bytes:10' : 10 bytes as a bytes object
|
||||
'bool' : 1 bit as a bool
|
||||
'pad:3' : 3 zero bits as padding
|
||||
|
||||
>>> s = pack('uint:12, bits', 100, '0xffe')
|
||||
>>> t = pack(['bits', 'bin:3'], s, '111')
|
||||
>>> u = pack('uint:8=a, uint:8=b, uint:55=a', a=6, b=44)
|
||||
|
||||
"""
|
||||
tokens = []
|
||||
if isinstance(fmt, str):
|
||||
fmt = [fmt]
|
||||
try:
|
||||
for f_item in fmt:
|
||||
_, tkns = tokenparser(f_item, tuple(sorted(kwargs.keys())))
|
||||
tokens.extend(tkns)
|
||||
except ValueError as e:
|
||||
raise CreationError(*e.args)
|
||||
value_iter = iter(values)
|
||||
bsl: List[BitStore] = []
|
||||
try:
|
||||
for name, length, value in tokens:
|
||||
# If the value is in the kwd dictionary then it takes precedence.
|
||||
value = kwargs.get(value, value)
|
||||
# If the length is in the kwd dictionary then use that too.
|
||||
length = kwargs.get(length, length)
|
||||
# Also if we just have a dictionary name then we want to use it
|
||||
if name in kwargs and length is None and value is None:
|
||||
bsl.append(BitStream(kwargs[name])._bitstore)
|
||||
continue
|
||||
if length is not None:
|
||||
length = int(length)
|
||||
if value is None and name != 'pad':
|
||||
# Take the next value from the ones provided
|
||||
value = next(value_iter)
|
||||
if name == 'bits':
|
||||
value = bitstring.bits.Bits(value)
|
||||
if length is not None and length != len(value):
|
||||
raise CreationError(f"Token with length {length} packed with value of length {len(value)}.")
|
||||
bsl.append(value._bitstore)
|
||||
continue
|
||||
bsl.append(bitstore_from_token(name, length, value))
|
||||
except StopIteration:
|
||||
raise CreationError(f"Not enough parameters present to pack according to the "
|
||||
f"format. {len(tokens)} values are needed.")
|
||||
|
||||
try:
|
||||
next(value_iter)
|
||||
except StopIteration:
|
||||
# Good, we've used up all the *values.
|
||||
s = BitStream()
|
||||
if bitstring.options.lsb0:
|
||||
bsl.reverse()
|
||||
for b in bsl:
|
||||
s._bitstore += b
|
||||
return s
|
||||
|
||||
raise CreationError(f"Too many parameters present to pack according to the format. Only {len(tokens)} values were expected.")
|
||||
206
code/.venv/lib/python3.12/site-packages/bitstring/mxfp.py
Normal file
206
code/.venv/lib/python3.12/site-packages/bitstring/mxfp.py
Normal file
@@ -0,0 +1,206 @@
|
||||
import array
|
||||
import math
|
||||
import struct
|
||||
import bitarray
|
||||
from bitstring.luts import mxfp_luts_compressed
|
||||
import zlib
|
||||
from typing import Optional
|
||||
|
||||
|
||||
def round_to_nearest_ties_to_even(lut_int_to_float, lower: int, f: float) -> Optional[int]:
|
||||
upper = lower + 1
|
||||
# Special case for LUTs without a negative zero.
|
||||
lower_float = 0.0 if lower == 128 else lut_int_to_float[lower]
|
||||
upper_float = lut_int_to_float[upper]
|
||||
if upper_float < lower_float:
|
||||
lower, upper = upper, lower
|
||||
lower_float, upper_float = upper_float, lower_float
|
||||
if f == lower_float:
|
||||
return lower
|
||||
if f == upper_float:
|
||||
return upper
|
||||
if lower_float < f < upper_float:
|
||||
d1 = f - lower_float
|
||||
d2 = upper_float - f
|
||||
if d1 < d2:
|
||||
return lower
|
||||
if d2 < d1:
|
||||
return upper
|
||||
return lower if lower % 2 == 0 else upper
|
||||
return None
|
||||
|
||||
|
||||
class MXFPFormat:
|
||||
"""Defining an MXFP micro-scaling floating point format"""
|
||||
|
||||
def __init__(self, exp_bits: int, mantissa_bits: int, bias: int, mxfp_overflow: str):
|
||||
self.exp_bits = exp_bits
|
||||
self.mantissa_bits = mantissa_bits
|
||||
self.bias = bias
|
||||
self.mxfp_overflow = mxfp_overflow
|
||||
|
||||
self.pos_clamp_value = (1 << (self.exp_bits + self.mantissa_bits)) - 1
|
||||
self.neg_clamp_value = (1 << (1 + self.exp_bits + self.mantissa_bits)) - 1
|
||||
|
||||
# Special cases for e4m3 and e5m2
|
||||
if self.exp_bits == 4 and self.mantissa_bits == 3:
|
||||
if self.mxfp_overflow == 'saturate':
|
||||
self.pos_clamp_value = 0b01111110 # 448
|
||||
self.neg_clamp_value = 0b11111110 # -448
|
||||
else:
|
||||
self.pos_clamp_value = self.neg_clamp_value = 0b11111111 # NaN
|
||||
if self.exp_bits == 5 and self.mantissa_bits == 2:
|
||||
if self.mxfp_overflow == 'saturate':
|
||||
self.pos_clamp_value = 0b01111011 # 57344
|
||||
self.neg_clamp_value = 0b11111011 # -57344
|
||||
else:
|
||||
self.pos_clamp_value = 0b01111100 # +inf
|
||||
self.neg_clamp_value = 0b11111100 # -inf
|
||||
|
||||
# If we calculate these LUTs now it creates a bootstrap problem in generate_luts.py.
|
||||
self.lut_float16_to_mxfp = None
|
||||
self.lut_int_to_float = None
|
||||
|
||||
def __str__(self):
|
||||
return f"MXFPFormat(exp_bits={self.exp_bits}, mantissa_bits={self.mantissa_bits}, bias={self.bias}, mxfp_overflow='{self.mxfp_overflow}')"
|
||||
|
||||
def decompress_luts(self):
|
||||
int_to_float_compressed, float16_to_mxfp_compressed = mxfp_luts_compressed[(self.exp_bits, self.mantissa_bits, self.bias, self.mxfp_overflow)]
|
||||
self.lut_float16_to_mxfp = zlib.decompress(float16_to_mxfp_compressed)
|
||||
dec = zlib.decompress(int_to_float_compressed)
|
||||
self.lut_int_to_float = struct.unpack(f'<{len(dec) // 4}f', dec)
|
||||
|
||||
def create_luts(self):
|
||||
self.lut_int_to_float = self.createLUT_for_int_to_float()
|
||||
self.lut_float16_to_mxfp = self.createLUT_for_float16_to_mxfp()
|
||||
|
||||
def float_to_int(self, f: float) -> int:
|
||||
"""Given a Python float convert to the best mxfp float (expressed as an int) that represents it."""
|
||||
# First convert the float to a float16, then a 16 bit uint
|
||||
try:
|
||||
b = struct.pack('>e', f)
|
||||
except (OverflowError, struct.error):
|
||||
# Return the largest representable positive or negative value
|
||||
return self.pos_clamp_value if f > 0 else self.neg_clamp_value
|
||||
|
||||
f16_int = int.from_bytes(b, byteorder='big')
|
||||
# Then use this as an index to our large LUT
|
||||
return self.lut_float16_to_mxfp[f16_int]
|
||||
|
||||
def slow_float_to_int(self, f: float) -> int:
|
||||
# Slow, but easier to follow than the faster version.
|
||||
# The output int has the binary sequence needed for the float.
|
||||
length = 1 + self.exp_bits + self.mantissa_bits
|
||||
values = 1 << length
|
||||
# First get the NaN case out of the way
|
||||
if math.isnan(f):
|
||||
if length == 8:
|
||||
return 0xff # Works for both e5m2 and e4m3
|
||||
# For smaller lengths, NaN isn't supported so we instead return an invalid value to detect later
|
||||
return 0xff
|
||||
# This is so we can distinguish between 0.0 and -0.0
|
||||
is_positive = math.copysign(1.0, f) == 1.0
|
||||
if is_positive:
|
||||
# Positive, so top bit is not set
|
||||
for i in range(values // 2 - 1):
|
||||
upper = self.lut_int_to_float[i + 1]
|
||||
if upper == float('inf'):
|
||||
break
|
||||
x = round_to_nearest_ties_to_even(self.lut_int_to_float, i, f)
|
||||
if x is not None:
|
||||
return x
|
||||
return self.pos_clamp_value
|
||||
else:
|
||||
# Negative, so top bit is set
|
||||
for i in range(values // 2, values - 1):
|
||||
lower = self.lut_int_to_float[i + 1]
|
||||
if lower == float('-inf'):
|
||||
break
|
||||
x = round_to_nearest_ties_to_even(self.lut_int_to_float, i, f)
|
||||
if x is not None:
|
||||
return x
|
||||
# Clip to negative max
|
||||
return self.neg_clamp_value
|
||||
|
||||
def createLUT_for_int_to_float(self) -> array.array:
|
||||
"""Create a LUT to convert an int in representing a MXFP float into a Python float"""
|
||||
i2f = []
|
||||
length = 1 + self.exp_bits + self.mantissa_bits
|
||||
for i in range(1 << length):
|
||||
b = bitarray.util.int2ba(i, length=length, endian='big', signed=False)
|
||||
sign = b[0]
|
||||
exponent = bitarray.util.ba2int(b[1:1 + self.exp_bits])
|
||||
significand = b[1 + self.exp_bits:]
|
||||
if exponent == 0:
|
||||
significand = bitarray.bitarray('0') + significand
|
||||
exponent = -self.bias + 1
|
||||
else:
|
||||
significand = bitarray.bitarray('1') + significand
|
||||
exponent -= self.bias
|
||||
f = float(bitarray.util.ba2int(significand)) / (2.0 ** self.mantissa_bits)
|
||||
f *= 2 ** exponent
|
||||
if length == 8:
|
||||
# Some special cases
|
||||
if self.exp_bits == 5:
|
||||
if i in [0b01111100, 0b11111100]:
|
||||
f = float('inf')
|
||||
if i in [0b01111101, 0b11111101, 0b01111110, 0b11111110, 0b01111111, 0b11111111]:
|
||||
f = float('nan')
|
||||
if self.exp_bits == 4:
|
||||
if i in [0b01111111, 0b11111111]:
|
||||
f = float('nan')
|
||||
i2f.append(f if not sign else -f)
|
||||
return array.array('f', i2f)
|
||||
|
||||
def createLUT_for_float16_to_mxfp(self) -> bytes:
|
||||
"""Create a LUT to convert a float16 into a MXFP format"""
|
||||
# Used to create the LUT that was compressed and stored for the fp8 code
|
||||
length = 1 + self.exp_bits + self.mantissa_bits
|
||||
if length == 8:
|
||||
import gfloat
|
||||
from gfloat.formats import format_info_ocp_e5m2, format_info_ocp_e4m3
|
||||
fi = format_info_ocp_e5m2 if self.exp_bits == 5 else format_info_ocp_e4m3
|
||||
|
||||
fp16_to_fp8 = bytearray(1 << 16)
|
||||
for i in range(1 << 16):
|
||||
b = struct.pack('>H', i)
|
||||
f, = struct.unpack('>e', b)
|
||||
fp = gfloat.round_float(fi, f, sat=self.mxfp_overflow == 'saturate')
|
||||
if math.isnan(fp):
|
||||
fp8_i = 0b11111111
|
||||
else:
|
||||
# Special case for negative zero
|
||||
if fp == 0.0 and math.copysign(1.0, fp) == -1.0:
|
||||
fp8_i = 0b10000000
|
||||
else:
|
||||
fp8_i = self.lut_int_to_float.index(fp)
|
||||
fp16_to_fp8[i] = fp8_i
|
||||
return bytes(fp16_to_fp8)
|
||||
else:
|
||||
assert length in [4, 6]
|
||||
fp16_to_fp8 = bytearray(1 << 16)
|
||||
for i in range(1 << 16):
|
||||
b = struct.pack('>H', i)
|
||||
f, = struct.unpack('>e', b)
|
||||
fp8_i = self.slow_float_to_int(f)
|
||||
fp16_to_fp8[i] = fp8_i
|
||||
return bytes(fp16_to_fp8)
|
||||
|
||||
|
||||
e2m1mxfp_fmt = MXFPFormat(exp_bits=2, mantissa_bits=1, bias=1, mxfp_overflow='saturate')
|
||||
e2m3mxfp_fmt = MXFPFormat(exp_bits=2, mantissa_bits=3, bias=1, mxfp_overflow='saturate')
|
||||
e3m2mxfp_fmt = MXFPFormat(exp_bits=3, mantissa_bits=2, bias=3, mxfp_overflow='saturate')
|
||||
e4m3mxfp_saturate_fmt = MXFPFormat(exp_bits=4, mantissa_bits=3, bias=7, mxfp_overflow='saturate')
|
||||
e5m2mxfp_saturate_fmt = MXFPFormat(exp_bits=5, mantissa_bits=2, bias=15, mxfp_overflow='saturate')
|
||||
e4m3mxfp_overflow_fmt = MXFPFormat(exp_bits=4, mantissa_bits=3, bias=7, mxfp_overflow='overflow')
|
||||
e5m2mxfp_overflow_fmt = MXFPFormat(exp_bits=5, mantissa_bits=2, bias=15, mxfp_overflow='overflow')
|
||||
|
||||
|
||||
def decompress_luts():
|
||||
e2m1mxfp_fmt.decompress_luts()
|
||||
e2m3mxfp_fmt.decompress_luts()
|
||||
e3m2mxfp_fmt.decompress_luts()
|
||||
e4m3mxfp_saturate_fmt.decompress_luts()
|
||||
e5m2mxfp_saturate_fmt.decompress_luts()
|
||||
e4m3mxfp_overflow_fmt.decompress_luts()
|
||||
e5m2mxfp_overflow_fmt.decompress_luts()
|
||||
238
code/.venv/lib/python3.12/site-packages/bitstring/utils.py
Normal file
238
code/.venv/lib/python3.12/site-packages/bitstring/utils.py
Normal file
@@ -0,0 +1,238 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import functools
|
||||
import re
|
||||
from typing import Tuple, List, Optional, Pattern, Dict, Union, Match
|
||||
|
||||
|
||||
# A token name followed by optional : then an integer number
|
||||
NAME_INT_RE: Pattern[str] = re.compile(r'^([a-zA-Z][a-zA-Z0-9_]*?):?(\d*)$')
|
||||
|
||||
# A token name followed by optional : then an arbitrary keyword
|
||||
NAME_KWARG_RE: Pattern[str] = re.compile(r'^([a-zA-Z][a-zA-Z0-9_]*?):?([a-zA-Z0-9_]+)$')
|
||||
|
||||
CACHE_SIZE = 256
|
||||
|
||||
DEFAULT_BITS: Pattern[str] = re.compile(r'^(?P<len>[^=]+)?(=(?P<value>.*))?$', re.IGNORECASE)
|
||||
|
||||
MULTIPLICATIVE_RE: Pattern[str] = re.compile(r'^(?P<factor>.*)\*(?P<token>.+)')
|
||||
|
||||
# Hex, oct or binary literals
|
||||
LITERAL_RE: Pattern[str] = re.compile(r'^(?P<name>0([xob]))(?P<value>.+)', re.IGNORECASE)
|
||||
|
||||
# An endianness indicator followed by one or more struct.pack codes
|
||||
STRUCT_PACK_RE: Pattern[str] = re.compile(r'^(?P<endian>[<>@=])(?P<fmt>(?:\d*[bBhHlLqQefd])+)$')
|
||||
# The same as above, but it doesn't insist on an endianness as it's byteswapping anyway.
|
||||
BYTESWAP_STRUCT_PACK_RE: Pattern[str] = re.compile(r'^(?P<endian>[<>@=])?(?P<fmt>(?:\d*[bBhHlLqQefd])+)$')
|
||||
# An endianness indicator followed by exactly one struct.pack codes
|
||||
SINGLE_STRUCT_PACK_RE: Pattern[str] = re.compile(r'^(?P<endian>[<>@=])(?P<fmt>[bBhHlLqQefd])$')
|
||||
|
||||
# A number followed by a single character struct.pack code
|
||||
STRUCT_SPLIT_RE: Pattern[str] = re.compile(r'\d*[bBhHlLqQefd]')
|
||||
|
||||
# These replicate the struct.pack codes
|
||||
# Big-endian
|
||||
REPLACEMENTS_BE: Dict[str, str] = {'b': 'int8', 'B': 'uint8',
|
||||
'h': 'intbe16', 'H': 'uintbe16',
|
||||
'l': 'intbe32', 'L': 'uintbe32',
|
||||
'q': 'intbe64', 'Q': 'uintbe64',
|
||||
'e': 'floatbe16', 'f': 'floatbe32', 'd': 'floatbe64'}
|
||||
# Little-endian
|
||||
REPLACEMENTS_LE: Dict[str, str] = {'b': 'int8', 'B': 'uint8',
|
||||
'h': 'intle16', 'H': 'uintle16',
|
||||
'l': 'intle32', 'L': 'uintle32',
|
||||
'q': 'intle64', 'Q': 'uintle64',
|
||||
'e': 'floatle16', 'f': 'floatle32', 'd': 'floatle64'}
|
||||
|
||||
# Native-endian
|
||||
REPLACEMENTS_NE: Dict[str, str] = {'b': 'int8', 'B': 'uint8',
|
||||
'h': 'intne16', 'H': 'uintne16',
|
||||
'l': 'intne32', 'L': 'uintne32',
|
||||
'q': 'intne64', 'Q': 'uintne64',
|
||||
'e': 'floatne16', 'f': 'floatne32', 'd': 'floatne64'}
|
||||
|
||||
# Size in bytes of all the pack codes.
|
||||
PACK_CODE_SIZE: Dict[str, int] = {'b': 1, 'B': 1, 'h': 2, 'H': 2, 'l': 4, 'L': 4,
|
||||
'q': 8, 'Q': 8, 'e': 2, 'f': 4, 'd': 8}
|
||||
|
||||
|
||||
def structparser(m: Match[str]) -> List[str]:
|
||||
"""Parse struct-like format string token into sub-token list."""
|
||||
endian = m.group('endian')
|
||||
# Split the format string into a list of 'q', '4h' etc.
|
||||
formatlist = re.findall(STRUCT_SPLIT_RE, m.group('fmt'))
|
||||
# Now deal with multiplicative factors, 4h -> hhhh etc.
|
||||
fmt = ''.join([f[-1] * int(f[:-1]) if len(f) != 1 else
|
||||
f for f in formatlist])
|
||||
if endian in '@=':
|
||||
# Native endianness
|
||||
tokens = [REPLACEMENTS_NE[c] for c in fmt]
|
||||
elif endian == '<':
|
||||
tokens = [REPLACEMENTS_LE[c] for c in fmt]
|
||||
else:
|
||||
assert endian == '>'
|
||||
tokens = [REPLACEMENTS_BE[c] for c in fmt]
|
||||
return tokens
|
||||
|
||||
|
||||
@functools.lru_cache(CACHE_SIZE)
|
||||
def parse_name_length_token(fmt: str, **kwargs) -> Tuple[str, Optional[int]]:
|
||||
# Any single token with just a name and length
|
||||
if m2 := NAME_INT_RE.match(fmt):
|
||||
name = m2.group(1)
|
||||
length_str = m2.group(2)
|
||||
length = None if length_str == '' else int(length_str)
|
||||
else:
|
||||
# Maybe the length is in the kwargs?
|
||||
if m := NAME_KWARG_RE.match(fmt):
|
||||
name = m.group(1)
|
||||
try:
|
||||
length_str = kwargs[m.group(2)]
|
||||
except KeyError:
|
||||
raise ValueError(f"Can't parse 'name[:]length' token '{fmt}'.")
|
||||
length = int(length_str)
|
||||
else:
|
||||
raise ValueError(f"Can't parse 'name[:]length' token '{fmt}'.")
|
||||
return name, length
|
||||
|
||||
|
||||
@functools.lru_cache(CACHE_SIZE)
|
||||
def parse_single_struct_token(fmt: str) -> Optional[Tuple[str, Optional[int]]]:
|
||||
if m := SINGLE_STRUCT_PACK_RE.match(fmt):
|
||||
endian = m.group('endian')
|
||||
f = m.group('fmt')
|
||||
if endian == '>':
|
||||
fmt = REPLACEMENTS_BE[f]
|
||||
elif endian == '<':
|
||||
fmt = REPLACEMENTS_LE[f]
|
||||
else:
|
||||
assert endian in '=@'
|
||||
fmt = REPLACEMENTS_NE[f]
|
||||
return parse_name_length_token(fmt)
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
@functools.lru_cache(CACHE_SIZE)
|
||||
def parse_single_token(token: str) -> Tuple[str, str, Optional[str]]:
|
||||
if (equals_pos := token.find('=')) == -1:
|
||||
value = None
|
||||
else:
|
||||
value = token[equals_pos + 1:]
|
||||
token = token[:equals_pos]
|
||||
|
||||
if m2 := NAME_INT_RE.match(token):
|
||||
name = m2.group(1)
|
||||
length_str = m2.group(2)
|
||||
length = None if length_str == '' else length_str
|
||||
elif m3 := NAME_KWARG_RE.match(token):
|
||||
# name then a keyword for a length
|
||||
name = m3.group(1)
|
||||
length = m3.group(2)
|
||||
else:
|
||||
# If you don't specify a 'name' then the default is 'bits'
|
||||
name = 'bits'
|
||||
length = token
|
||||
return name, length, value
|
||||
|
||||
|
||||
@functools.lru_cache(CACHE_SIZE)
|
||||
def preprocess_tokens(fmt: str) -> List[str]:
|
||||
# Remove whitespace and expand brackets
|
||||
fmt = expand_brackets(''.join(fmt.split()))
|
||||
|
||||
# Split tokens by ',' and remove whitespace
|
||||
# The meta_tokens can either be ordinary single tokens or multiple struct-format token strings.
|
||||
meta_tokens = [f.strip() for f in fmt.split(',')]
|
||||
final_tokens = []
|
||||
|
||||
for meta_token in meta_tokens:
|
||||
if meta_token == '':
|
||||
continue
|
||||
# Extract factor and actual token if a multiplicative factor exists
|
||||
factor = 1
|
||||
if m := MULTIPLICATIVE_RE.match(meta_token):
|
||||
factor = int(m.group('factor'))
|
||||
meta_token = m.group('token')
|
||||
|
||||
# Parse struct-like format into sub-tokens or treat as single token
|
||||
tokens = structparser(m) if (m := STRUCT_PACK_RE.match(meta_token)) else [meta_token]
|
||||
|
||||
# Extend final tokens list with parsed tokens, repeated by the factor
|
||||
final_tokens.extend(tokens * factor)
|
||||
return final_tokens
|
||||
|
||||
|
||||
@functools.lru_cache(CACHE_SIZE)
|
||||
def tokenparser(fmt: str, keys: Tuple[str, ...] = ()) -> \
|
||||
Tuple[bool, List[Tuple[str, Union[int, str, None], Optional[str]]]]:
|
||||
"""Divide the format string into tokens and parse them.
|
||||
|
||||
Return stretchy token and list of [initialiser, length, value]
|
||||
initialiser is one of: hex, oct, bin, uint, int, se, ue, 0x, 0o, 0b etc.
|
||||
length is None if not known, as is value.
|
||||
|
||||
If the token is in the keyword dictionary (keys) then it counts as a
|
||||
special case and isn't messed with.
|
||||
|
||||
tokens must be of the form: [factor*][initialiser][:][length][=value]
|
||||
|
||||
"""
|
||||
tokens = preprocess_tokens(fmt)
|
||||
stretchy_token = False
|
||||
ret_vals: List[Tuple[str, Union[str, int, None], Optional[str]]] = []
|
||||
for token in tokens:
|
||||
if keys and token in keys:
|
||||
# Don't bother parsing it, it's a keyword argument
|
||||
ret_vals.append((token, None, None))
|
||||
continue
|
||||
if token == '':
|
||||
continue
|
||||
# Match literal tokens of the form 0x... 0o... and 0b...
|
||||
if m := LITERAL_RE.match(token):
|
||||
ret_vals.append((m.group('name'), None, m.group('value')))
|
||||
continue
|
||||
name, length, value = parse_single_token(token)
|
||||
if length is None:
|
||||
stretchy_token = True
|
||||
if length is not None:
|
||||
# Try converting length to int, otherwise check it's a key.
|
||||
try:
|
||||
length = int(length)
|
||||
except ValueError:
|
||||
if not keys or length not in keys:
|
||||
raise ValueError(f"Don't understand length '{length}' of token.")
|
||||
ret_vals.append((name, length, value))
|
||||
return stretchy_token, ret_vals
|
||||
|
||||
|
||||
BRACKET_RE = re.compile(r'(?P<factor>\d+)\*\(')
|
||||
|
||||
|
||||
def expand_brackets(s: str) -> str:
|
||||
"""Expand all brackets."""
|
||||
while True:
|
||||
start = s.find('(')
|
||||
if start == -1:
|
||||
break
|
||||
count = 1 # Number of hanging open brackets
|
||||
p = start + 1
|
||||
while p < len(s):
|
||||
count += (s[p] == '(') - (s[p] == ')')
|
||||
if count == 0:
|
||||
break
|
||||
p += 1
|
||||
if count != 0:
|
||||
raise ValueError(f"Unbalanced parenthesis in '{s}'.")
|
||||
if start == 0 or s[start - 1] != '*':
|
||||
s = s[0:start] + s[start + 1:p] + s[p + 1:]
|
||||
else:
|
||||
# Looks for first number*(
|
||||
m = BRACKET_RE.search(s)
|
||||
if m:
|
||||
factor = int(m.group('factor'))
|
||||
matchstart = m.start('factor')
|
||||
s = s[0:matchstart] + (factor - 1) * (s[start + 1:p] + ',') + s[start + 1:p] + s[p + 1:]
|
||||
else:
|
||||
raise ValueError(f"Failed to parse '{s}'.")
|
||||
return s
|
||||
Reference in New Issue
Block a user