pax_global_header 0000666 0000000 0000000 00000000064 14531676336 0014530 g ustar 00root root 0000000 0000000 52 comment=bf0d6e46dec489078b9ef19502245c9ddc153bf1
bitstring-bitstring-4.1.4/ 0000775 0000000 0000000 00000000000 14531676336 0015546 5 ustar 00root root 0000000 0000000 bitstring-bitstring-4.1.4/.github/ 0000775 0000000 0000000 00000000000 14531676336 0017106 5 ustar 00root root 0000000 0000000 bitstring-bitstring-4.1.4/.github/workflows/ 0000775 0000000 0000000 00000000000 14531676336 0021143 5 ustar 00root root 0000000 0000000 bitstring-bitstring-4.1.4/.github/workflows/ci.yml 0000664 0000000 0000000 00000011474 14531676336 0022270 0 ustar 00root root 0000000 0000000 name: CI
on:
push:
branches:
- stable
- main
- 4.1-update
tags:
- v*
pull_request:
branches:
- '**'
concurrency:
group: ${{ github.ref }}-${{ github.workflow }}-${{ github.event_name }}-${{ github.ref == 'refs/heads/main' && github.sha || '' }}
cancel-in-progress: true
defaults:
run:
shell: bash
jobs:
build:
name: Build ${{ matrix.os.name }} ${{ matrix.python.name }}
runs-on: ${{ matrix.os.runs-on }}
strategy:
fail-fast: false
matrix:
os:
- name: 🐧
runs-on: ubuntu-latest
python:
- name: CPython 3.10
major_dot_minor: '3.10'
action: '3.10'
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
# This allows the matrix to specify just the major.minor version while still
# expanding it to get the latest patch version including alpha releases.
# This avoids the need to update for each new alpha, beta, release candidate,
# and then finally an actual release version. actions/setup-python doesn't
# support this for PyPy presently so we get no help there.
#
# CPython -> 3.9.0-alpha - 3.9.X
# PyPy -> pypy-3.7
python-version: ${{ fromJSON(format('["{0}", "{1}"]', format('{0}.0-alpha - {0}.X', matrix.python.action), matrix.python.action))[startsWith(matrix.python.action, 'pypy')] }}
architecture: x64
- name: Setup environment
run: |
python -m pip install --upgrade pip
python -m pip install build
- name: Build sdist and wheel
run: |
python -m build
- name: Publish package files
if: always()
uses: actions/upload-artifact@v3
with:
name: packages
path: dist/*
if-no-files-found: error
test:
name: Test ${{ matrix.os.name }} ${{ matrix.python.name }}
needs:
- build
runs-on: ${{ matrix.os.runs-on }}
strategy:
fail-fast: false
matrix:
os:
- name: 🐧
runs-on: ubuntu-latest
- name: 🍎
runs-on: macos-latest
- name: 🪟
runs-on: windows-latest
python:
- name: CPython 3.7
major_dot_minor: '3.7'
action: '3.7'
- name: CPython 3.8
major_dot_minor: '3.8'
action: '3.8'
- name: CPython 3.9
major_dot_minor: '3.9'
action: '3.9'
- name: CPython 3.10
major_dot_minor: '3.10'
action: '3.10'
- name: CPython 3.11
major_dot_minor: '3.11'
action: '3.11'
# Failing due to error using temporary file in a unittest. I think it's a PyPy bug, not a bitstring one!
# - name: PyPy 3.7
# major_dot_minor: '3.7'
# action: 'pypy-3.7'
# - name: PyPy 3.8
# major_dot_minor: '3.8'
# action: 'pypy-3.8'
steps:
- uses: actions/checkout@v3
with:
path: repo
- name: Download package files
uses: actions/download-artifact@v3
with:
name: packages
path: dist
- uses: actions/setup-python@v4
with:
# This allows the matrix to specify just the major.minor version while still
# expanding it to get the latest patch version including alpha releases.
# This avoids the need to update for each new alpha, beta, release candidate,
# and then finally an actual release version. actions/setup-python doesn't
# support this for PyPy presently so we get no help there.
#
# CPython -> 3.9.0-alpha - 3.9.X
# PyPy -> pypy-3.7
python-version: ${{ fromJSON(format('["{0}", "{1}"]', format('{0}.0-alpha - {0}.X', matrix.python.action), matrix.python.action))[startsWith(matrix.python.action, 'pypy')] }}
architecture: x64
- name: Setup environment
run: |
python --version --version
# make sure we test the installed code
cp -R repo/tests/ tests/
python -m pip install --upgrade pip
python -m pip install ./dist/*.whl
# show the directory contents for diagnostics
ls -la
- name: Run unittest
run: |
python -m unittest
all:
name: All successful
runs-on: ubuntu-latest
# The always() part is very important.
# If not set, the job will be skipped on failing dependencies.
if: always()
needs:
# This is the list of CI job that we are interested to be green before
# a merge.
- build
- test
steps:
- name: Require all successes
uses: re-actors/alls-green@v1.2.2
with:
jobs: ${{ toJSON(needs) }}
bitstring-bitstring-4.1.4/.gitignore 0000664 0000000 0000000 00000000065 14531676336 0017537 0 ustar 00root root 0000000 0000000 venv
.git
*cache*
.idea
build
dist
manifest
*egg-info bitstring-bitstring-4.1.4/.readthedocs.yaml 0000664 0000000 0000000 00000001301 14531676336 0020770 0 ustar 00root root 0000000 0000000 # .readthedocs.yaml
# Read the Docs configuration file
# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
# Required
version: 2
# Set the version of Python and other tools you might need
build:
os: ubuntu-22.04
tools:
python: "3.11"
# You can also specify other tool versions:
# nodejs: "16"
# rust: "1.55"
# golang: "1.17"
# Build documentation in the docs/ directory with Sphinx
sphinx:
configuration: doc/conf.py
# If using Sphinx, optionally build your docs in additional formats such as PDF
#formats:
# - pdf
# Optionally declare the Python requirements required to build your docs
python:
install:
- requirements: doc/requirements.txt bitstring-bitstring-4.1.4/LICENSE 0000664 0000000 0000000 00000002122 14531676336 0016550 0 ustar 00root root 0000000 0000000 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.
bitstring-bitstring-4.1.4/MANIFEST.in 0000664 0000000 0000000 00000000256 14531676336 0017307 0 ustar 00root root 0000000 0000000 include tests/test.m1v
include tests/smalltestfile
include tests/__init__.py
include release_notes.txt
include README.md
include bitstring/py.typed
prune doc
include LICENSE
bitstring-bitstring-4.1.4/README.md 0000664 0000000 0000000 00000011134 14531676336 0017025 0 ustar 00root root 0000000 0000000

**bitstring** is a Python module to help make the creation and analysis of binary data as simple and efficient as possible.
It has been maintained since 2006 and now has many millions of downloads per year.
[](https://github.com/scott-griffiths/bitstring/actions/workflows/ci.yml)
[](https://bitstring.readthedocs.io/en/latest/)
[](https://app.codacy.com/gh/scott-griffiths/bitstring/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_grade)
[](https://pypistats.org/packages/bitstring)
[](https://mybinder.org/v2/gh/scott-griffiths/bitstring/main?labpath=doc%2Fwalkthrough.ipynb)
News
----
**November 2023**: bitstring 4.1.3 released. Version 4.1 is a large update in terms of how much of the code has changed.
* Speed increased with bitarray dependency.
* New Array class for homogeneous data.
* Support for 8-bit floating point values.
See the [release notes](https://github.com/scott-griffiths/bitstring/blob/main/release_notes.txt) for details. Please let me know if you encounter any problems.
Overview
--------
* Efficiently store and manipulate binary data in idiomatic Python.
* Create bitstrings from hex, octal, binary, files, formatted strings, bytes, integers and floats of different endiannesses.
* Powerful binary packing and unpacking functions.
* Bit-level slicing, joining, searching, replacing and more.
* Create and manipulate arrays of fixed-length bitstrings.
* Read from and interpret bitstrings as streams of binary data.
* Rich API - chances are that whatever you want to do there's a simple and elegant way of doing it.
* Open source software, released under the MIT licence.
Documentation
-------------
Extensive documentation for the bitstring module is available.
Some starting points are given below:
* [Overview](https://bitstring.readthedocs.io/en/stable/index.html)
* [Quick Reference](https://bitstring.readthedocs.io/en/stable/quick_reference.html)
* [Full Reference](https://bitstring.readthedocs.io/en/stable/reference.html)
You can also try out the interactive walkthrough notebook on [binder](https://mybinder.org/v2/gh/scott-griffiths/bitstring/main?labpath=doc%2Fwalkthrough.ipynb).
Release Notes
-------------
To see what been added, improved or fixed, and also to see what's coming in the next version, see the [release notes](https://github.com/scott-griffiths/bitstring/blob/main/release_notes.txt).
Examples
--------
### Installation
$ pip install bitstring
### Creation
>>> from bitstring import Bits, BitArray, BitStream, pack
>>> a = BitArray(bin='00101')
>>> b = Bits(a_file_object)
>>> c = BitArray('0xff, 0b101, 0o65, uint6=22')
>>> d = pack('intle16, hex=a, 0b1', 100, a='0x34f')
>>> e = pack('<16h', *range(16))
### Different interpretations, slicing and concatenation
>>> a = BitArray('0x3348')
>>> a.hex, a.bin, a.uint, a.float, a.bytes
('3348', '0011001101001000', 13128, 0.2275390625, b'3H')
>>> a[10:3:-1].bin
'0101100'
>>> '0b100' + 3*a
BitArray('0x866906690669, 0b000')
### Reading data sequentially
>>> b = BitStream('0x160120f')
>>> b.read(12).hex
'160'
>>> b.pos = 0
>>> b.read('uint12')
352
>>> b.readlist('uint12, bin3')
[288, '111']
### Searching, inserting and deleting
>>> c = BitArray('0b00010010010010001111') # c.hex == '0x1248f'
>>> c.find('0x48')
(8,)
>>> c.replace('0b001', '0xabc')
>>> c.insert('0b0000', pos=3)
>>> del c[12:16]
### Arrays of fixed-length formats
>>> from bitstring import Array
>>> a = Array('uint7', [9, 100, 3, 1])
>>> a.data
BitArray('0x1390181')
>>> a[::2] *= 5
>>> a
Array('uint7', [45, 100, 15, 1])
Unit Tests
----------
The 700+ unit tests should all pass. They can be run from the root of the project with
python -m unittest
Credits
-------
Created by Scott Griffiths in 2006 to help with ad hoc parsing and creation of compressed video files.
Maintained and expanded ever since as it became unexpectedly popular. Thanks to all those who have contributed ideas
and code (and bug reports) over the years.
Copyright (c) 2006 - 2023 Scott Griffiths
bitstring-bitstring-4.1.4/bitstring/ 0000775 0000000 0000000 00000000000 14531676336 0017553 5 ustar 00root root 0000000 0000000 bitstring-bitstring-4.1.4/bitstring/__init__.py 0000664 0000000 0000000 00000022070 14531676336 0021665 0 ustar 00root root 0000000 0000000 #!/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.
Functions:
pack -- Create a BitStream from a format string.
Module Properties:
bytealigned -- Determines whether a number of methods default to working only on byte boundaries.
lsb0 -- If True, the least significant bit (the final bit) is indexed as bit zero.
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.1.4"
__author__ = "Scott Griffiths"
import sys
from .bits import Bits
from .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 MetaDtype, Register
import types
from typing import List, Tuple
from .utils import initialise_constants
# We initialise the Options singleton after the base classes have been created.
# This avoids a nasty circular import.
options = Options()
Bits._initialise_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/
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
dtypes = [
MetaDtype('uint', "a two's complement unsigned int",
Bits._setuint, Bits._readuint, Bits._getuint, True, False, False, False, None),
MetaDtype('uintle', "a two's complement little-endian unsigned int",
Bits._setuintle, Bits._readuintle, Bits._getuintle, True, False, False, False, None),
MetaDtype('uintne', "a two's complement native-endian unsigned int",
Bits._setuintne, Bits._readuintne, Bits._getuintne, True, False, False, False, None),
MetaDtype('uintbe', "a two's complement big-endian unsigned int",
Bits._setuintbe, Bits._readuintbe, Bits._getuintbe, True, False, False, False, None),
MetaDtype('int', "a two's complement signed int",
Bits._setint, Bits._readint, Bits._getint,True, False, True, False, None),
MetaDtype('intle', "a two's complement little-endian signed int",
Bits._setintle, Bits._readintle, Bits._getintle, True, False, True, False, None),
MetaDtype('intne', "a two's complement native-endian signed int",
Bits._setintne, Bits._readintne, Bits._getintne, True, False, True, False, None),
MetaDtype('intbe', "a two's complement big-endian signed int",
Bits._setintbe, Bits._readintbe, Bits._getintbe, True, False, True, False, None),
MetaDtype('hex', 'a hexadecimal string',
Bits._sethex, Bits._readhex, Bits._gethex, False, False, False, False, None),
MetaDtype('bin', 'a binary string',
Bits._setbin_safe, Bits._readbin, Bits._getbin, False, False, False, False, None),
MetaDtype('oct', 'an octal string',
Bits._setoct, Bits._readoct, Bits._getoct,False, False, False, False, None),
MetaDtype('e5m2float', 'an 8 bit float with e5m2float format',
Bits._sete5m2float, Bits._reade5m2float, Bits._gete5m2float, False, True, True, False, 8),
MetaDtype('e4m3float', 'an 8 bit float with e4m3float format',
Bits._sete4m3float, Bits._reade4m3float, Bits._gete4m3float, False, True, True, False, 8),
MetaDtype('float', 'a big-endian floating point number',
Bits._setfloatbe, Bits._readfloatbe, Bits._getfloatbe, False, True, True, False, None),
MetaDtype('floatne', 'a native-endian floating point number',
Bits._setfloatne, Bits._readfloatne, Bits._getfloatne, False, True, True, False, None),
MetaDtype('floatle', 'a little-endian floating point number',
Bits._setfloatle, Bits._readfloatle, Bits._getfloatle, False, True, True, False, None),
MetaDtype('bfloat', 'a 16 bit big-endian bfloat floating point number',
Bits._setbfloatbe, Bits._readbfloatbe, Bits._getbfloatbe, False, True, True, False, 16),
MetaDtype('bfloatle', 'a 16 bit little-endian bfloat floating point number',
Bits._setbfloatle, Bits._readbfloatle, Bits._getbfloatle, False, True, True, False, 16),
MetaDtype('bfloatne', 'a 16 bit native-endian bfloat floating point number',
Bits._setbfloatne, Bits._readbfloatne, Bits._getbfloatne, False, True, True, False, 16),
MetaDtype('bits', 'a bitstring object',
Bits._setbits, Bits._readbits, None, False, False, False, False, None),
MetaDtype('bytes', 'a bytes object',
Bits._setbytes, Bits._readbytes, Bits._getbytes,False, False, False, False, None),
MetaDtype('bool', 'a bool (True or False)',
Bits._setbool, Bits._readbool, Bits._getbool, True, False, False, False, 1),
MetaDtype('se', 'a signed exponential-Golomb code',
Bits._setse, Bits._readse, Bits._getse,True, False, True, True, None),
MetaDtype('ue', 'an unsigned exponential-Golomb code',
Bits._setue, Bits._readue, Bits._getue, True, False, False, True, None),
MetaDtype('sie', 'a signed interleaved exponential-Golomb code',
Bits._setsie, Bits._readsie, Bits._getsie, True, False, True, True, None),
MetaDtype('uie', 'an unsigned interleaved exponential-Golomb code',
Bits._setuie, Bits._readuie, Bits._getuie, True, False, False, True, None),
MetaDtype('pad', 'a skipped section of padding',
None, Bits._readpad, None, False, False, False, False, None),
]
aliases: List[Tuple[str, str]] = [
('float', 'floatbe'),
('bfloat', 'bfloatbe'),
('int', 'i'),
('uint', 'u'),
('hex', 'h'),
('oct', 'o'),
('bin', 'b'),
('float', 'f')
]
register = Register()
for dt in dtypes:
register.add_meta_dtype(dt)
for alias in aliases:
register.add_meta_dtype_alias(alias[0], alias[1])
# Create properties for those meta dtypes that have a 'get' function.
for dt_name in register.name_to_meta_dtype:
dt = register.name_to_meta_dtype[dt_name]
if dt.get_fn is not None:
setattr(Bits, dt_name, property(fget=dt.get_fn, doc=f"The bitstring as {dt.description}. Read only."))
setattr(BitArray, dt_name, property(fget=dt.get_fn, fset=dt.set_fn, doc=f"The bitstring as {dt.description}. Read and write."))
init_names = [dt_name for dt_name in register.name_to_meta_dtype]
unknowable_length_names = register.unknowable_length_names()
initialise_constants(init_names, unknowable_length_names)
__all__ = ['ConstBitStream', 'BitStream', 'BitArray', 'Array',
'Bits', 'pack', 'Error', 'ReadError', 'InterpretError',
'ByteAlignError', 'CreationError', 'bytealigned', 'lsb0']
bitstring-bitstring-4.1.4/bitstring/__main__.py 0000664 0000000 0000000 00000003223 14531676336 0021645 0 ustar 00root root 0000000 0000000 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.name_to_meta_dtype:
# 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.name_to_meta_dtype:
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() bitstring-bitstring-4.1.4/bitstring/array_.py 0000664 0000000 0000000 00000101447 14531676336 0021411 0 ustar 00root root 0000000 0000000 from __future__ import annotations
import math
import numbers
from collections.abc import Sized
from bitstring.exceptions import CreationError, InterpretError
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, Register
from bitstring.utils import tokenparser, parse_name_length_token
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]
dtype_register = Register()
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()
try:
self.dtype = dtype
except ValueError as e:
raise CreationError(e)
if isinstance(initializer, numbers.Integral):
self.data = BitArray(initializer * self._dtype.length)
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)
@property
def itemsize(self) -> int:
return self._dtype.length
@property
def trailing_bits(self) -> BitArray:
trailing_bit_length = len(self.data) % self._dtype.length
return BitArray() if trailing_bit_length == 0 else self.data[-trailing_bit_length:]
# Converting array.array typecodes to our equivalents.
_array_typecodes: 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'}
@property
def dtype(self) -> str:
return self._fmt
@dtype.setter
def dtype(self, new_dtype: Union[str, Dtype]) -> None:
if isinstance(new_dtype, Dtype):
self._dtype = new_dtype
self._fmt = str(self._dtype)
else:
dtype = dtype_register.get_dtype(*parse_name_length_token(new_dtype))
if dtype.length == 0:
raise ValueError(f"A fixed length format is needed for an Array, received '{new_dtype}'.")
self._dtype = dtype
self._fmt = new_dtype
def _create_element(self, value: ElementType) -> Bits:
"""Create Bits from value according to the token_name and token_length"""
b = Bits()
self._dtype.set_fn(b, value)
if len(b) != self._dtype.length:
raise ValueError(f"The value {value!r} has the wrong length for the format '{self._fmt}'.")
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 = Array(self._dtype)
a.data = d
return a
else:
a = Array(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 {stop - start}.")
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._fmt}', {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 = Array(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]) -> 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._fmt}' from an Array of format '{iterable._fmt}'.")
# No need to iterate over the elements, we can just append the data
self.data.append(iterable.data)
elif isinstance(iterable, array.array):
other_fmt = Array._array_typecodes.get(iterable.typecode, iterable.typecode)
token_name, token_length, _ = tokenparser(other_fmt)[1][0]
if self._dtype.name != token_name or self._dtype.length != token_length:
raise ValueError(
f"Cannot extend an Array with format '{self._fmt}' 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._fmt}' 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.length
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.length} 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.length]
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 = False, 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.
"""
sep = ' '
fmt_is_dtype = False
if fmt is None:
fmt = self.dtype
fmt_is_dtype = True
tokens = tokenparser(fmt)[1]
token_names_and_lengths = [(x[0], x[1]) for x in tokens]
if len(token_names_and_lengths) not in [1, 2]:
raise ValueError(
f"Only one or two tokens can be used in an Array.pp() format - '{fmt}' has {len(token_names_and_lengths)} tokens.")
token_name, token_length = token_names_and_lengths[0]
token_name2, token_length2 = None, None
getter_func2 = None
if len(token_names_and_lengths) == 1:
if token_length is None:
token_length = self.itemsize
fmt += str(token_length)
if len(token_names_and_lengths) == 2:
token_name2, token_length2 = token_names_and_lengths[1]
if token_length is None and token_length2 is None:
token_length = token_length2 = self.itemsize
fmt += str(token_length)
if token_length is None:
token_length = token_length2
if token_length2 is None:
token_length2 = token_length
if token_length != token_length2:
raise ValueError(f"Two different format lengths specified ('{fmt}'). Either specify just one, or two the same length.")
getter_func2 = dtype_register.get_dtype(token_name2, token_length2).read_fn
getter_func = dtype_register.get_dtype(token_name, token_length).read_fn
# Check that the getter functions will work
temp = BitArray(token_length)
try:
getter_func(temp, 0)
except InterpretError as e:
raise ValueError(f"Pretty print format not valid: {e.msg}")
if token_name2 is not None:
try:
getter_func2(temp, 0)
except InterpretError as e:
raise ValueError(f"Pretty print format not valid: {e.msg}")
trailing_bit_length = len(self.data) % token_length
format_sep = " : " # String to insert on each line between multiple formats
if trailing_bit_length == 0:
data = self.data
else:
data = self.data[0: -trailing_bit_length]
length = len(self.data) // token_length
parameter_name = "dtype" if fmt_is_dtype else "fmt"
stream.write(f"\n[\n")
data._pp(token_name, token_name2, token_length, width, sep, format_sep, show_offset, stream, False, token_length, getter_func, getter_func2)
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 = Array(self._fmt)
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 = Array('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._fmt}.")
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 == operator.add or op == operator.iadd:
msg += " Use extend() if you want to concatenate Arrays."
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 = Array(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.
"""
if type1.is_float + type1.is_integer + type2.is_float + type2.is_integer != 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 type1.is_float and type2.is_integer:
return type1
if type1.is_integer and type2.is_float:
return type2
if type1.is_float and type2.is_float:
return type2 if type2.length > type1.length else type1
assert type1.is_integer and type2.is_integer
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__(self, other: Union[int, float, str, BitsType, Array]) -> Array:
if isinstance(other, Array):
return self._apply_op_between_arrays(operator.eq, other, is_comparison=True)
return self._apply_op_to_all_elements(operator.eq, other, is_comparison=True)
def __ne__(self, other: Union[int, float, str, BitsType, Array]) -> Array:
if isinstance(other, Array):
return self._apply_op_between_arrays(operator.ne, other, is_comparison=True)
return self._apply_op_to_all_elements(operator.ne, other, is_comparison=True)
# 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) bitstring-bitstring-4.1.4/bitstring/bitarray.py 0000664 0000000 0000000 00000060326 14531676336 0021751 0 ustar 00root root 0000000 0000000 from __future__ import annotations
import copy
import numbers
import re
from collections import abc
from typing import Union, List, Iterable, Any, Optional, Pattern, Dict, Callable
from bitstring.utils import BYTESWAP_STRUCT_PACK_RE, STRUCT_SPLIT_RE, PACK_CODE_SIZE
from bitstring.exceptions import CreationError, Error
from bitstring.bits import Bits, BitsType, TBits
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.
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:
bin -- The bitstring as a binary string.
hex -- The bitstring as a hexadecimal string.
oct -- The bitstring as an octal string.
bytes -- The bitstring as a bytes object.
int -- Interpret as a two's complement signed integer.
uint -- Interpret as a two's complement unsigned integer.
float / floatbe -- Interpret as a big-endian floating point number.
bool -- For single bit bitstrings, interpret as True or False.
se -- Interpret as a signed exponential-Golomb code.
ue -- Interpret as an unsigned exponential-Golomb code.
sie -- Interpret as a signed interleaved exponential-Golomb code.
uie -- Interpret as an unsigned interleaved exponential-Golomb code.
floatle -- Interpret as a little-endian floating point number.
floatne -- Interpret as a native-endian floating point number.
bfloat / bfloatbe -- Interpret as a big-endian 16-bit bfloat type.
bfloatle -- Interpret as a little-endian 16-bit bfloat type.
bfloatne -- Interpret as a native-endian 16-bit bfloat type.
intbe -- Interpret as a big-endian signed integer.
intle -- Interpret as a little-endian signed integer.
intne -- Interpret as a native-endian signed integer.
uintbe -- Interpret as a big-endian unsigned integer.
uintle -- Interpret as a little-endian unsigned integer.
uintne -- Interpret as a native-endian unsigned integer.
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
_letter_to_setter: Dict[str, Callable[..., None]] = \
{'u': Bits._setuint,
'i': Bits._setint,
'f': Bits._setfloatbe,
'b': Bits._setbin_safe,
'o': Bits._setoct,
'h': Bits._sethex}
_name_length_pattern: Pattern[str] = re.compile(r'^(?P[a-z]+)(?P\d+)$', re.IGNORECASE)
def __setattr__(self, attribute, value) -> None:
try:
# First try the ordinary attribute setter
super().__setattr__(attribute, value)
except AttributeError:
name_length = BitArray._name_length_pattern.match(attribute)
if name_length:
name = name_length.group('name')
length = name_length.group('len')
if length is not None:
length = int(length)
if name == 'bytes':
if len(value) != length:
raise CreationError(
f"Wrong amount of byte data preset - {length} bytes needed, have {len(value)} bytes.")
length *= 8
try:
x = Bits(length=length, offset=None, **{name: value})
if len(x) != length:
raise CreationError(f"Can't initialise with value of length {len(x)} bits, "
f"as attribute has length of {length} bits.")
self._bitstore = x._bitstore
return
except AttributeError:
pass
raise AttributeError(f"Can't set attribute {attribute} with value {value}.")
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 + self.len 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):
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)
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(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 self.len:
raise ValueError("Cannot shift an empty bitstring.")
if not n:
return self
n = min(n, self.len)
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 self.len:
raise ValueError("Cannot shift an empty bitstring.")
if not n:
return self
n = min(n, self.len)
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)
if self.len != bs.len:
raise ValueError("Bitstrings must have the same length for |= operator.")
self._bitstore |= bs._bitstore
return self
def __iand__(self: TBits, bs: BitsType) -> TBits:
bs = self._create_from_bitstype(bs)
if self.len != bs.len:
raise ValueError("Bitstrings must have the same length for &= operator.")
self._bitstore &= bs._bitstore
return self
def __ixor__(self: TBits, bs: BitsType) -> TBits:
bs = self._create_from_bitstype(bs)
if self.len != bs.len:
raise ValueError("Bitstrings must have the same length for ^= operator.")
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 = BitArray._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] + old.len:
# 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(slice(0, starting_points[0], None))]
for i in range(len(starting_points) - 1):
replacement_list.append(new._bitstore)
replacement_list.append(
self._bitstore.getslice(slice(starting_points[i] + old.len, starting_points[i + 1], None)))
# Final replacement
replacement_list.append(new._bitstore)
replacement_list.append(self._bitstore.getslice(slice(starting_points[-1] + old.len, None, None)))
if BitArray._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 not old.len:
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 not bs.len:
return
if bs is self:
bs = self._copy()
if pos < 0:
pos += self._getlength()
if not 0 <= pos <= self._getlength():
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 not bs.len:
return
if pos < 0:
pos += self._getlength()
if pos < 0 or pos > self.len:
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 == self.len:
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 = self.len
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 self.len:
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 self.len:
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):
m = BYTESWAP_STRUCT_PACK_RE.match(fmt)
if not m:
raise ValueError(f"Cannot parse format string {fmt}.")
# 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.
bytesizes = []
for f in formatlist:
if len(f) == 1:
bytesizes.append(PACK_CODE_SIZE[f])
else:
bytesizes.extend([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()
bitstring-bitstring-4.1.4/bitstring/bits.py 0000664 0000000 0000000 00000257055 14531676336 0021104 0 ustar 00root root 0000000 0000000 from __future__ import annotations
import numbers
import pathlib
import sys
import re
import mmap
import struct
import array
import io
from collections import abc
import functools
from typing import Tuple, Union, List, Iterable, Any, Optional, Pattern, Dict, \
BinaryIO, TextIO, Callable, overload, Iterator, Type, TypeVar
import bitarray
import bitarray.util
from bitstring.utils import tokenparser
from bitstring.exceptions import CreationError, InterpretError, ReadError, Error
from bitstring.fp8 import e4m3float_fmt, e5m2float_fmt
from bitstring.bitstore import BitStore, offset_slice_indices_lsb0
from bitstring.bitstore_helpers import float2bitstore, uint2bitstore, ue2bitstore, str_to_bitstore, se2bitstore, \
bfloat2bitstore, floatle2bitstore, uintbe2bitstore, uintle2bitstore, intbe2bitstore, intle2bitstore, bfloatle2bitstore, \
bin2bitstore, bin2bitstore_unsafe, hex2bitstore, int2bitstore, oct2bitstore, sie2bitstore, uie2bitstore
# Things that can be converted to Bits when a Bits type is needed
BitsType = Union['Bits', str, Iterable[Any], bool, BinaryIO, bytearray, bytes, memoryview, bitarray.bitarray]
TBits = TypeVar("TBits", bound='Bits')
byteorder: str = sys.byteorder
# Maximum number of digits to use in __str__ and __repr__.
MAX_CHARS: int = 250
class Bits:
"""A container holding an immutable sequence of bits.
For a mutable container use the BitArray class instead.
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.
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.
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:
Also available are the operators [], ==, !=, +, *, ~, <<, >>, &, |, ^.
Properties:
bin -- The bitstring as a binary string.
hex -- The bitstring as a hexadecimal string.
oct -- The bitstring as an octal string.
bytes -- The bitstring as a bytes object.
int -- Interpret as a two's complement signed integer.
uint -- Interpret as a two's complement unsigned integer.
float / floatbe -- Interpret as a big-endian floating point number.
bool -- For single bit bitstrings, interpret as True or False.
se -- Interpret as a signed exponential-Golomb code.
ue -- Interpret as an unsigned exponential-Golomb code.
sie -- Interpret as a signed interleaved exponential-Golomb code.
uie -- Interpret as an unsigned interleaved exponential-Golomb code.
floatle -- Interpret as a little-endian floating point number.
floatne -- Interpret as a native-endian floating point number.
bfloat / bfloatbe -- Interpret as a big-endian 16-bit bfloat type.
bfloatle -- Interpret as a little-endian 16-bit bfloat type.
bfloatne -- Interpret as a native-endian 16-bit bfloat type.
intbe -- Interpret as a big-endian signed integer.
intle -- Interpret as a little-endian signed integer.
intne -- Interpret as a native-endian signed integer.
uintbe -- Interpret as a big-endian unsigned integer.
uintle -- Interpret as a little-endian unsigned integer.
uintne -- Interpret as a native-endian unsigned integer.
len -- Length of the bitstring in bits.
"""
__slots__ = ('_bitstore')
_options = None
_register = None
@classmethod
def _initialise_options(cls):
# To avoid circular imports this happens after all the classes are initialised.
from .options import Options
cls._options = Options()
from .dtypes import Register
cls._register = Register()
# Creates dictionaries to quickly reverse single bytes
_int8ReversalDict: Dict[int, int] = {i: int("{0:08b}".format(i)[::-1], 2) for i in range(0x100)}
_byteReversalDict: Dict[int, bytes] = {i: bytes([int("{0:08b}".format(i)[::-1], 2)]) for i in range(0x100)}
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, an array 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'.
"""
self._bitstore.immutable = True
def __new__(cls: Type[TBits], __auto: Optional[Union[BitsType, int]] = None, length: Optional[int] = None,
offset: Optional[int] = None, pos: Optional[int] = None, **kwargs) -> TBits:
x = object.__new__(cls)
if __auto is None and not kwargs:
# No initialiser so fill with zero bits up to length
if length is not None:
x._bitstore = BitStore(length)
x._bitstore.setall(0)
else:
x._bitstore = BitStore()
return x
x._initialise(__auto, length, offset, **kwargs)
return x
@classmethod
def _create_empty_instance(cls):
x = object.__new__(cls)
x._bitstore = BitStore._create_empty_instance()
return x
@classmethod
def _create_from_bitstype(cls: Type[TBits], auto: Optional[BitsType]) -> TBits:
b = cls._create_empty_instance()
if auto is None:
return b
b._setauto(auto, None, None)
return b
def _initialise(self, __auto: Any, length: Optional[int], offset: Optional[int], **kwargs) -> None:
if length is not None and length < 0:
raise CreationError("bitstring length cannot be negative.")
if offset is not None and offset < 0:
raise CreationError("offset must be >= 0.")
if __auto is not None:
if isinstance(__auto, numbers.Integral):
# Initialise with s zero bits.
if __auto < 0:
raise CreationError(f"Can't create bitstring of negative length {__auto}.")
self._bitstore = BitStore(int(__auto))
self._bitstore.setall(0)
return
self._setauto(__auto, length, offset)
return
k, v = kwargs.popitem()
try:
setting_function = Bits._register.name_to_meta_dtype[k].set_fn
except KeyError:
if k == 'filename':
setting_function = Bits._setfile
elif k == 'bitarray':
setting_function = Bits._setbitarray
elif k == 'auto':
raise CreationError(f"The 'auto' parameter should not be given explicitly - just use the first positional argument. "
f"Instead of '{self.__class__.__name__}(auto=x)' use '{self.__class__.__name__}(x)'.")
else:
raise CreationError(f"Unrecognised keyword '{k}' used to initialise.")
setting_function(self, v, length, offset)
def __getattr__(self, attribute: str) -> Any:
# Support for arbitrary attributes like u16 or f64.
# Try to split into [name][length], then try standard properties
name_length_pattern: Pattern[str] = re.compile(r'^(?P[a-z]+):?(?P\d+)$', re.IGNORECASE)
name_length = name_length_pattern.match(attribute)
if name_length:
name = name_length.group('name')
length = int(name_length.group('len'))
if name == 'bytes' and length is not None:
length *= 8
if length is not None and self.len != int(length):
raise InterpretError(f"bitstring length {self.len} doesn't match length of property {attribute}.")
try:
return getattr(self, name)
except AttributeError:
pass
raise AttributeError(f"'{self.__class__.__name__}' object has no attribute '{attribute}'.")
def __iter__(self) -> Iterable[bool]:
return iter(self._bitstore)
def __copy__(self: TBits) -> TBits:
"""Return a new copy of the Bits for the copy module."""
# Note that if you want a new copy (different ID), use _copy instead.
# The copy can return self as it's immutable.
return self
def __lt__(self, other: Any) -> bool:
# bitstrings can't really be ordered.
return NotImplemented
def __gt__(self, other: Any) -> bool:
return NotImplemented
def __le__(self, other: Any) -> bool:
return NotImplemented
def __ge__(self, other: Any) -> bool:
return NotImplemented
def __add__(self: TBits, bs: BitsType) -> TBits:
"""Concatenate bitstrings and return new bitstring.
bs -- the bitstring to append.
"""
bs = self.__class__._create_from_bitstype(bs)
if bs.len <= self.len:
s = self._copy()
s._addright(bs)
else:
s = bs._copy()
s = self.__class__(s)
s._addleft(self)
return s
def __radd__(self: TBits, bs: BitsType) -> TBits:
"""Append current bitstring to bs and return new bitstring.
bs -- An object that can be 'auto' initialised as a bitstring that will be appended to.
"""
bs = self.__class__._create_from_bitstype(bs)
return bs.__add__(self)
@overload
def __getitem__(self: TBits, key: slice) -> TBits:
...
@overload
def __getitem__(self, 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.
Indices are in units of the step parameter (default 1 bit).
Stepping is used to specify the number of bits in each item.
>>> print(BitArray('0b00110')[1:4])
'0b011'
>>> print(BitArray('0x00112233')[1:3:8])
'0x1122'
"""
if isinstance(key, numbers.Integral):
return bool(self._bitstore.getindex(key))
x = self._bitstore.getslice(key)
bs = self.__class__()
bs._bitstore = x
return bs
def __len__(self) -> int:
"""Return the length of the bitstring in bits."""
return self._getlength()
def __bytes__(self) -> bytes:
return self.tobytes()
def __str__(self) -> str:
"""Return approximate string representation of bitstring for printing.
Short strings will be given wholly in hexadecimal or binary. Longer
strings may be part hexadecimal and part binary. Very long strings will
be truncated with '...'.
"""
length = self.len
if not length:
return ''
if length > MAX_CHARS * 4:
# Too long for hex. Truncate...
return ''.join(('0x', self._readhex(0, MAX_CHARS * 4), '...'))
# If it's quite short and we can't do hex then use bin
if length < 32 and length % 4 != 0:
return '0b' + self.bin
# If we can use hex then do so
if not length % 4:
return '0x' + self.hex
# Otherwise first we do as much as we can in hex
# then add on 1, 2 or 3 bits on at the end
bits_at_end = length % 4
return ''.join(('0x', self._readhex(0, length - bits_at_end),
', ', '0b',
self._readbin(length - bits_at_end, bits_at_end)))
def _repr(self, classname: str, length: int, offset: int, filename: str, pos: int):
pos_string = f', pos={pos}' if pos else ''
if filename:
offsetstring = f', offset={offset}' if offset else ''
return f"{classname}(filename={repr(filename)}, length={length}{offsetstring}{pos_string})"
else:
s = self.__str__()
lengthstring = ''
if s.endswith('...'):
lengthstring = f' # length={length}'
return f"{classname}('{s}'{pos_string}){lengthstring}"
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._bitstore.offset, self._bitstore.filename, 0)
def __eq__(self, bs: Any) -> bool:
"""Return True if two bitstrings have the same binary representation.
>>> BitArray('0b1110') == '0xe'
True
"""
try:
bs = Bits._create_from_bitstype(bs)
except TypeError:
return False
return self._bitstore == bs._bitstore
def __ne__(self, bs: Any) -> bool:
"""Return False if two bitstrings have the same binary representation.
>>> BitArray('0b111') == '0x7'
False
"""
return not self.__eq__(bs)
def __invert__(self: TBits) -> TBits:
"""Return bitstring with every bit inverted.
Raises Error if the bitstring is empty.
"""
if not self.len:
raise Error("Cannot invert empty bitstring.")
s = self._copy()
s._invert_all()
return s
def __lshift__(self: TBits, n: int) -> TBits:
"""Return bitstring with bits shifted by n to the left.
n -- the number of bits to shift. Must be >= 0.
"""
if n < 0:
raise ValueError("Cannot shift by a negative amount.")
if not self.len:
raise ValueError("Cannot shift an empty bitstring.")
n = min(n, self.len)
s = self._absolute_slice(n, self.len)
s._addright(Bits(n))
return s
def __rshift__(self: TBits, n: int) -> TBits:
"""Return bitstring with bits shifted by n to the right.
n -- the number of bits to shift. Must be >= 0.
"""
if n < 0:
raise ValueError("Cannot shift by a negative amount.")
if not self.len:
raise ValueError("Cannot shift an empty bitstring.")
if not n:
return self._copy()
s = self.__class__(length=min(n, self.len))
n = min(n, self.len)
s._addright(self._absolute_slice(0, self.len - n))
return s
def __mul__(self: TBits, n: int) -> TBits:
"""Return bitstring consisting of n concatenations of self.
Called for expression of the form 'a = b*3'.
n -- The number of concatenations. Must be >= 0.
"""
if n < 0:
raise ValueError("Cannot multiply by a negative integer.")
if not n:
return self.__class__()
s = self._copy()
s._imul(n)
return s
def __rmul__(self: TBits, n: int) -> TBits:
"""Return bitstring consisting of n concatenations of self.
Called for expressions of the form 'a = 3*b'.
n -- The number of concatenations. Must be >= 0.
"""
return self.__mul__(n)
def __and__(self: TBits, bs: BitsType) -> TBits:
"""Bit-wise 'and' between two bitstrings. Returns new bitstring.
bs -- The bitstring to '&' with.
Raises ValueError if the two bitstrings have differing lengths.
"""
bs = Bits._create_from_bitstype(bs)
if self.len != bs.len:
raise ValueError("Bitstrings must have the same length for & operator.")
s = self._copy()
s._bitstore &= bs._bitstore
return s
def __rand__(self: TBits, bs: BitsType) -> TBits:
"""Bit-wise 'and' between two bitstrings. Returns new bitstring.
bs -- the bitstring to '&' with.
Raises ValueError if the two bitstrings have differing lengths.
"""
return self.__and__(bs)
def __or__(self: TBits, bs: BitsType) -> TBits:
"""Bit-wise 'or' between two bitstrings. Returns new bitstring.
bs -- The bitstring to '|' with.
Raises ValueError if the two bitstrings have differing lengths.
"""
bs = Bits._create_from_bitstype(bs)
if self.len != bs.len:
raise ValueError("Bitstrings must have the same length for | operator.")
s = self._copy()
s._bitstore |= bs._bitstore
return s
def __ror__(self: TBits, bs: BitsType) -> TBits:
"""Bit-wise 'or' between two bitstrings. Returns new bitstring.
bs -- The bitstring to '|' with.
Raises ValueError if the two bitstrings have differing lengths.
"""
return self.__or__(bs)
def __xor__(self: TBits, bs: BitsType) -> TBits:
"""Bit-wise 'xor' between two bitstrings. Returns new bitstring.
bs -- The bitstring to '^' with.
Raises ValueError if the two bitstrings have differing lengths.
"""
bs = Bits._create_from_bitstype(bs)
if self.len != bs.len:
raise ValueError("Bitstrings must have the same length for ^ operator.")
s = self._copy()
s._bitstore ^= bs._bitstore
return s
def __rxor__(self: TBits, bs: BitsType) -> TBits:
"""Bit-wise 'xor' between two bitstrings. Returns new bitstring.
bs -- The bitstring to '^' with.
Raises ValueError if the two bitstrings have differing lengths.
"""
return self.__xor__(bs)
def __contains__(self, bs: BitsType) -> bool:
"""Return whether bs is contained in the current bitstring.
bs -- The bitstring to search for.
"""
found = Bits.find(self, bs, bytealigned=False)
return bool(found)
def __hash__(self) -> int:
"""Return an integer hash of the object."""
# Only requirement is that equal bitstring should return the same hash.
# For equal bitstrings the bytes at the start/end will be the same and they will have the same length
# (need to check the length as there could be zero padding when getting the bytes). We do not check any
# bit position inside the bitstring as that does not feature in the __eq__ operation.
if self.len <= 2000:
# Use the whole bitstring.
return hash((self.tobytes(), self.len))
else:
# We can't in general hash the whole bitstring (it could take hours!)
# So instead take some bits from the start and end.
return hash(((self[:800] + self[-800:]).tobytes(), self.len))
def __bool__(self) -> bool:
"""Return True if any bits are set to 1, otherwise return False."""
return len(self) != 0
def _clear(self) -> None:
"""Reset the bitstring to an empty state."""
self._bitstore = BitStore()
def _setauto(self, s: BitsType, length: Optional[int], offset: Optional[int]) -> None:
"""Set bitstring from a bitstring, file, bool, array, iterable or string."""
# As s can be so many different things it's important to do the checks
# in the correct order, as some types are also other allowed types.
# So str must be checked before Iterable
# and bytes/bytearray before Iterable but after str!
if offset is None:
offset = 0
if isinstance(s, Bits):
if length is None:
length = s._getlength() - offset
self._bitstore = s._bitstore.getslice(slice(offset, offset + length, None))
return
if isinstance(s, io.BytesIO):
if length is None:
length = s.seek(0, 2) * 8 - offset
byteoffset, offset = divmod(offset, 8)
bytelength = (length + byteoffset * 8 + offset + 7) // 8 - byteoffset
if length + byteoffset * 8 + offset > s.seek(0, 2) * 8:
raise CreationError("BytesIO object is not long enough for specified length and offset.")
self._bitstore = BitStore(frombytes=s.getvalue()[byteoffset: byteoffset + bytelength]).getslice(
slice(offset, offset + length))
return
if isinstance(s, io.BufferedReader):
m = mmap.mmap(s.fileno(), 0, access=mmap.ACCESS_READ)
self._bitstore = BitStore(buffer=m, offset=offset, length=length, filename=s.name, immutable=True)
return
if isinstance(s, bitarray.bitarray):
if length is None:
if offset > len(s):
raise CreationError(f"Offset of {offset} too large for bitarray of length {len(s)}.")
self._bitstore = BitStore(s[offset:])
else:
if offset + length > len(s):
raise CreationError(
f"Offset of {offset} and length of {length} too large for bitarray of length {len(s)}.")
self._bitstore = BitStore(s[offset: offset + length])
return
if length is not None:
raise CreationError("The length keyword isn't applicable to this initialiser.")
if offset > 0:
raise CreationError("The offset keyword isn't applicable to this initialiser.")
if isinstance(s, str):
self._bitstore = str_to_bitstore(s)
return
if isinstance(s, (bytes, bytearray, memoryview)):
self._bitstore = BitStore(frombytes=bytearray(s))
return
if isinstance(s, array.array):
self._bitstore = BitStore(frombytes=bytearray(s.tobytes()))
return
if isinstance(s, abc.Iterable):
# Evaluate each item as True or False and set bits to 1 or 0.
self._setbin_unsafe(''.join(str(int(bool(x))) for x in s))
return
if isinstance(s, numbers.Integral):
raise TypeError(f"It's no longer possible to auto initialise a bitstring from an integer."
f" Use '{self.__class__.__name__}({s})' instead of just '{s}' as this makes it "
f"clearer that a bitstring of {int(s)} zero bits will be created.")
raise TypeError(f"Cannot initialise bitstring from {type(s)}.")
def _setfile(self, filename: str, length: Optional[int], offset: Optional[int]) -> None:
"""Use file as source of bits."""
with open(pathlib.Path(filename), 'rb') as source:
if offset is None:
offset = 0
m = mmap.mmap(source.fileno(), 0, access=mmap.ACCESS_READ)
self._bitstore = BitStore(buffer=m, offset=offset, length=length, filename=source.name, immutable=True)
def _setbitarray(self, ba: bitarray.bitarray, length: Optional[int], offset: Optional[int]) -> None:
if offset is None:
offset = 0
if offset > len(ba):
raise CreationError(f"Offset of {offset} too large for bitarray of length {len(ba)}.")
if length is None:
self._bitstore = BitStore(ba[offset:])
else:
if offset + length > len(ba):
raise CreationError(
f"Offset of {offset} and length of {length} too large for bitarray of length {len(ba)}.")
self._bitstore = BitStore(ba[offset: offset + length])
def _setbits(self, bs: BitsType, length: None = None, offset: None = None) -> None:
bs = Bits._create_from_bitstype(bs)
self._bitstore = bs._bitstore
def _sete5m2float(self, f: float, length: None = None, _offset: None = None):
u = e5m2float_fmt.float_to_int8(f)
self._bitstore = uint2bitstore(u, 8)
def _sete4m3float(self, f: float, length: None = None, _offset: None = None):
u = e4m3float_fmt.float_to_int8(f)
self._bitstore = uint2bitstore(u, 8)
def _setbytes(self, data: Union[bytearray, bytes],
length: Optional[int] = None, offset: Optional[int] = None) -> None:
"""Set the data from a bytes or bytearray object."""
if offset is None and length is None:
self._bitstore = BitStore(frombytes=bytearray(data))
return
data = bytearray(data)
if offset is None:
offset = 0
if length is None:
# Use to the end of the data
length = len(data) * 8 - offset
else:
if length + offset > len(data) * 8:
raise CreationError(f"Not enough data present. Need {length + offset} bits, have {len(data) * 8}.")
self._bitstore = BitStore(buffer=data).getslice_msb0(slice(offset, offset + length, None))
def _readbytes(self, start: int, length: int) -> bytes:
"""Read bytes and return them. Note that length is in bits."""
assert length % 8 == 0
assert start + length <= self.len
return self._bitstore.getslice(slice(start, start + length, None)).tobytes()
def _getbytes(self) -> bytes:
"""Return the data as an ordinary bytes object."""
if self.len % 8:
raise InterpretError("Cannot interpret as bytes unambiguously - not multiple of 8 bits.")
return self._readbytes(0, self.len)
_unprintable = list(range(0x00, 0x20)) # ASCII control characters
_unprintable.extend(range(0x7f, 0xff)) # DEL char + non-ASCII
def _getbytes_printable(self) -> str:
"""Return an approximation of the data as a string of printable characters."""
bytes_ = self._getbytes()
# For everything that isn't printable ASCII, use value from 'Latin Extended-A' unicode block.
string = ''.join(chr(0x100 + x) if x in Bits._unprintable else chr(x) for x in bytes_)
return string
def _setuint(self, uint: int, length: Optional[int] = None, _offset: None = None) -> None:
"""Reset the bitstring to have given unsigned int interpretation."""
# If no length given, and we've previously been given a length, use it.
if length is None and hasattr(self, 'len') and self.len != 0:
length = self.len
if length is None or length == 0:
raise CreationError("A non-zero length must be specified with a uint initialiser.")
if _offset is not None:
raise CreationError("An offset can't be specified with an integer initialiser.")
self._bitstore = uint2bitstore(uint, length)
def _readuint(self, start: int, length: int) -> int:
"""Read bits and interpret as an unsigned int."""
if length == 0:
raise InterpretError("Cannot interpret a zero length bitstring as an integer.")
ip = bitarray.util.ba2int(self._bitstore.getslice(slice(start, start + length, None)), signed=False)
return ip
def _getuint(self) -> int:
"""Return data as an unsigned int."""
if self.len == 0:
raise InterpretError("Cannot interpret a zero length bitstring as an integer.")
bs = self._bitstore.copy() if self._bitstore.modified else self._bitstore
return bitarray.util.ba2int(bs, signed=False)
def _setint(self, int_: int, length: Optional[int] = None, _offset: None = None) -> None:
"""Reset the bitstring to have given signed int interpretation."""
# If no length given, and we've previously been given a length, use it.
if length is None and hasattr(self, 'len') and self.len != 0:
length = self.len
if length is None or length == 0:
raise CreationError("A non-zero length must be specified with an int initialiser.")
if _offset is not None:
raise CreationError("An offset can't be specified with an integer initialiser.")
self._bitstore = int2bitstore(int_, length)
def _readint(self, start: int, length: int) -> int:
"""Read bits and interpret as a signed int"""
if length == 0:
raise InterpretError("Cannot interpret bitstring without a length as an integer.")
ip = bitarray.util.ba2int(self._bitstore.getslice(slice(start, start + length, None)), signed=True)
return ip
def _getint(self) -> int:
"""Return data as a two's complement signed int."""
if self.len == 0:
raise InterpretError("Cannot interpret bitstring without a length as an integer.")
bs = self._bitstore.copy() if self._bitstore.modified else self._bitstore
return bitarray.util.ba2int(bs, signed=True)
def _setuintbe(self, uintbe: int, length: Optional[int] = None, _offset: None = None) -> None:
"""Set the bitstring to a big-endian unsigned int interpretation."""
if length is None and hasattr(self, 'len') and self.len != 0:
length = self.len
if length is None or length == 0:
raise CreationError("A non-zero length must be specified with a uintbe initialiser.")
self._bitstore = uintbe2bitstore(uintbe, length)
def _readuintbe(self, start: int, length: int) -> int:
"""Read bits and interpret as a big-endian unsigned int."""
if length % 8:
raise InterpretError(f"Big-endian integers must be whole-byte. Length = {length} bits.")
return self._readuint(start, length)
def _getuintbe(self) -> int:
"""Return data as a big-endian two's complement unsigned int."""
return self._readuintbe(0, self.len)
def _setintbe(self, intbe: int, length: Optional[int] = None, _offset: None = None) -> None:
"""Set bitstring to a big-endian signed int interpretation."""
if length is None and hasattr(self, 'len') and self.len != 0:
length = self.len
if length is None or length == 0:
raise CreationError("A non-zero length must be specified with a intbe initialiser.")
self._bitstore = intbe2bitstore(intbe, length)
def _readintbe(self, start: int, length: int) -> int:
"""Read bits and interpret as a big-endian signed int."""
if length % 8:
raise InterpretError(f"Big-endian integers must be whole-byte. Length = {length} bits.")
return self._readint(start, length)
def _getintbe(self) -> int:
"""Return data as a big-endian two's complement signed int."""
return self._readintbe(0, self.len)
def _setuintle(self, uintle: int, length: Optional[int] = None, _offset: None = None) -> None:
if length is None and hasattr(self, 'len') and self.len != 0:
length = self.len
if length is None or length == 0:
raise CreationError("A non-zero length must be specified with a uintle initialiser.")
if _offset is not None:
raise CreationError("An offset can't be specified with an integer initialiser.")
self._bitstore = uintle2bitstore(uintle, length)
def _readuintle(self, start: int, length: int) -> int:
"""Read bits and interpret as a little-endian unsigned int."""
if length % 8:
raise InterpretError(f"Little-endian integers must be whole-byte. Length = {length} bits.")
bs = BitStore(frombytes=self._bitstore.getslice(slice(start, start + length, None)).tobytes()[::-1])
val = bitarray.util.ba2int(bs, signed=False)
return val
def _getuintle(self) -> int:
return self._readuintle(0, self.len)
def _setintle(self, intle: int, length: Optional[int] = None, _offset: None = None) -> None:
if length is None and hasattr(self, 'len') and self.len != 0:
length = self.len
if length is None or length == 0:
raise CreationError("A non-zero length must be specified with an intle initialiser.")
if _offset is not None:
raise CreationError("An offset can't be specified with an integer initialiser.")
self._bitstore = intle2bitstore(intle, length)
def _readintle(self, start: int, length: int) -> int:
"""Read bits and interpret as a little-endian signed int."""
if length % 8:
raise InterpretError(f"Little-endian integers must be whole-byte. Length = {length} bits.")
bs = BitStore(frombytes=self._bitstore.getslice(slice(start, start + length, None)).tobytes()[::-1])
val = bitarray.util.ba2int(bs, signed=True)
return val
def _getintle(self) -> int:
return self._readintle(0, self.len)
def _readfloat(self, start: int, length: int, struct_dict: Dict[int, str]) -> float:
"""Read bits and interpret as a float."""
try:
fmt = struct_dict[length]
except KeyError:
raise InterpretError(f"Floats can only be 16, 32 or 64 bits long, not {length} bits")
offset = start % 8
if offset == 0:
return struct.unpack(fmt, self._bitstore.getslice(slice(start, start + length, None)).tobytes())[0]
else:
return struct.unpack(fmt, self._readbytes(start, length))[0]
def _reade4m3float(self, start: int, length: int = 0) -> float:
# length is ignored - it's only present to make the function signature consistent.
u = self._readuint(start, length=8)
return e4m3float_fmt.lut_int8_to_float[u]
def _gete4m3float(self) -> float:
if len(self) != 8:
raise InterpretError(f"A e4m3float must be 8 bits long, not {len(self)} bits.")
return self._reade4m3float(0)
def _reade5m2float(self, start: int, length: int = 0) -> float:
# length is ignored - it's only present to make the function signature consistent.
u = self._readuint(start, length=8)
return e5m2float_fmt.lut_int8_to_float[u]
def _gete5m2float(self) -> float:
if len(self) != 8:
raise InterpretError(f"A e5m2float must be 8 bits long, not {len(self)} bits.")
return self._reade5m2float(start=0)
def _setfloatbe(self, f: float, length: Optional[int] = None, _offset: None = None) -> None:
if length is None and hasattr(self, 'len') and self.len != 0:
length = self.len
if length is None or length not in [16, 32, 64]:
raise CreationError("A length of 16, 32, or 64 must be specified with a float initialiser.")
self._bitstore = float2bitstore(f, length)
def _readfloatbe(self, start: int, length: int) -> float:
"""Read bits and interpret as a big-endian float."""
return self._readfloat(start, length, {16: '>e', 32: '>f', 64: '>d'})
def _getfloatbe(self) -> float:
"""Interpret the whole bitstring as a big-endian float."""
return self._readfloatbe(0, self.len)
def _setfloatle(self, f: float, length: Optional[int] = None, _offset: None = None) -> None:
if length is None and hasattr(self, 'len') and self.len != 0:
length = self.len
if length is None or length not in [16, 32, 64]:
raise CreationError("A length of 16, 32, or 64 must be specified with a float initialiser.")
self._bitstore = floatle2bitstore(f, length)
def _readfloatle(self, start: int, length: int) -> float:
"""Read bits and interpret as a little-endian float."""
return self._readfloat(start, length, {16: ' float:
"""Interpret the whole bitstring as a little-endian float."""
return self._readfloatle(0, self.len)
def _getbfloatbe(self) -> float:
return self._readbfloatbe(0, self.len)
def _readbfloatbe(self, start: int, length: int) -> float:
if length != 16:
raise InterpretError(f"bfloats must be length 16, received a length of {length} bits.")
two_bytes = self._slice(start, start + 16)
zero_padded = two_bytes + Bits(16)
return zero_padded._getfloatbe()
def _setbfloatbe(self, f: Union[float, str], length: Optional[int] = None, _offset: None = None) -> None:
if length is not None and length != 16:
raise CreationError(f"bfloats must be length 16, received a length of {length} bits.")
self._bitstore = bfloat2bitstore(f)
def _getbfloatle(self) -> float:
return self._readbfloatle(0, self.len)
def _readbfloatle(self, start: int, length: int) -> float:
two_bytes = self._slice(start, start + 16)
zero_padded = Bits(16) + two_bytes
return zero_padded._getfloatle()
def _setbfloatle(self, f: Union[float, str], length: Optional[int] = None, _offset: None = None) -> None:
if length is not None and length != 16:
raise CreationError(f"bfloats must be length 16, received a length of {length} bits.")
self._bitstore = bfloatle2bitstore(f)
def _setue(self, i: int, _length: None = None, _offset: None = None) -> None:
"""Initialise bitstring with unsigned exponential-Golomb code for integer i.
Raises CreationError if i < 0.
"""
if _length is not None or _offset is not None:
raise CreationError("Cannot specify a length of offset for exponential-Golomb codes.")
if Bits._options.lsb0:
raise CreationError("Exp-Golomb codes cannot be used in lsb0 mode.")
self._bitstore = ue2bitstore(i)
def _readue(self, pos: int, length: int = 0) -> Tuple[int, int]:
"""Return interpretation of next bits as unsigned exponential-Golomb code.
Raises ReadError if the end of the bitstring is encountered while
reading the code.
"""
# _length is ignored - it's only present to make the function signature consistent.
if Bits._options.lsb0:
raise ReadError("Exp-Golomb codes cannot be read in lsb0 mode.")
oldpos = pos
try:
while not self[pos]:
pos += 1
except IndexError:
raise ReadError("Read off end of bitstring trying to read code.")
leadingzeros = pos - oldpos
codenum = (1 << leadingzeros) - 1
if leadingzeros > 0:
if pos + leadingzeros + 1 > self.len:
raise ReadError("Read off end of bitstring trying to read code.")
codenum += self._readuint(pos + 1, leadingzeros)
pos += leadingzeros + 1
else:
assert codenum == 0
pos += 1
return codenum, pos
def _getue(self) -> int:
"""Return data as unsigned exponential-Golomb code.
Raises InterpretError if bitstring is not a single exponential-Golomb code.
"""
try:
value, newpos = self._readue(0)
if value is None or newpos != self.len:
raise ReadError
except ReadError:
raise InterpretError("Bitstring is not a single exponential-Golomb code.")
return value
def _setse(self, i: int, _length: None = None, _offset: None = None) -> None:
"""Initialise bitstring with signed exponential-Golomb code for integer i."""
if _length is not None or _offset is not None:
raise CreationError("Cannot specify a length of offset for exponential-Golomb codes.")
if Bits._options.lsb0:
raise CreationError("Exp-Golomb codes cannot be used in lsb0 mode.")
self._bitstore = se2bitstore(i)
def _getse(self) -> int:
"""Return data as signed exponential-Golomb code.
Raises InterpretError if bitstring is not a single exponential-Golomb code.
"""
try:
value, newpos = self._readse(0)
if value is None or newpos != self.len:
raise ReadError
except ReadError:
raise InterpretError("Bitstring is not a single exponential-Golomb code.")
return value
def _readse(self, pos: int, length: int = 0) -> Tuple[int, int]:
"""Return interpretation of next bits as a signed exponential-Golomb code.
Advances position to after the read code.
Raises ReadError if the end of the bitstring is encountered while
reading the code.
"""
codenum, pos = self._readue(pos)
m = (codenum + 1) // 2
if not codenum % 2:
return -m, pos
else:
return m, pos
def _setuie(self, i: int, _length: None = None, _offset: None = None) -> None:
"""Initialise bitstring with unsigned interleaved exponential-Golomb code for integer i.
Raises CreationError if i < 0.
"""
if _length is not None or _offset is not None:
raise CreationError("Cannot specify a length of offset for exponential-Golomb codes.")
if Bits._options.lsb0:
raise CreationError("Exp-Golomb codes cannot be used in lsb0 mode.")
self._bitstore = uie2bitstore(i)
def _readuie(self, pos: int, length: int = 0) -> Tuple[int, int]:
"""Return interpretation of next bits as unsigned interleaved exponential-Golomb code.
Raises ReadError if the end of the bitstring is encountered while
reading the code.
"""
# _length is ignored - it's only present to make the function signature consistent.
if Bits._options.lsb0:
raise ReadError("Exp-Golomb codes cannot be read in lsb0 mode.")
try:
codenum: int = 1
while not self[pos]:
pos += 1
codenum <<= 1
codenum += self[pos]
pos += 1
pos += 1
except IndexError:
raise ReadError("Read off end of bitstring trying to read code.")
codenum -= 1
return codenum, pos
def _getuie(self) -> int:
"""Return data as unsigned interleaved exponential-Golomb code.
Raises InterpretError if bitstring is not a single exponential-Golomb code.
"""
try:
value, newpos = self._readuie(0)
if value is None or newpos != self.len:
raise ReadError
except ReadError:
raise InterpretError("Bitstring is not a single interleaved exponential-Golomb code.")
return value
def _setsie(self, i: int, length: None = None, _offset: None = None) -> None:
"""Initialise bitstring with signed interleaved exponential-Golomb code for integer i."""
if length is not None or _offset is not None:
raise CreationError("Cannot specify a length of offset for exponential-Golomb codes.")
if Bits._options.lsb0:
raise CreationError("Exp-Golomb codes cannot be used in lsb0 mode.")
self._bitstore = sie2bitstore(i)
def _getsie(self) -> int:
"""Return data as signed interleaved exponential-Golomb code.
Raises InterpretError if bitstring is not a single exponential-Golomb code.
"""
try:
value, newpos = self._readsie(0)
if value is None or newpos != self.len:
raise ReadError
except ReadError:
raise InterpretError("Bitstring is not a single interleaved exponential-Golomb code.")
return value
def _readsie(self, pos: int, length: int = 0) -> Tuple[int, int]:
"""Return interpretation of next bits as a signed interleaved exponential-Golomb code.
Advances position to after the read code.
Raises ReadError if the end of the bitstring is encountered while
reading the code.
"""
codenum, pos = self._readuie(pos)
if not codenum:
return 0, pos
try:
if self[pos]:
return -codenum, pos + 1
else:
return codenum, pos + 1
except IndexError:
raise ReadError("Read off end of bitstring trying to read code.")
def _setbool(self, value: Union[bool, str], length: Optional[int] = None, _offset: None = None) -> None:
# We deliberately don't want to have implicit conversions to bool here.
# If we did then it would be difficult to deal with the 'False' string.
if length is not None and length != 1:
raise CreationError(f"bools must be length 1, received a length of {length} bits.")
if value in (1, 'True'):
self._bitstore = BitStore('1')
elif value in (0, 'False'):
self._bitstore = BitStore('0')
else:
raise CreationError(f"Cannot initialise boolean with {value}.")
def _getbool(self) -> bool:
if self.length != 1:
raise InterpretError(f"For a bool interpretation a bitstring must be 1 bit long, not {self.length} bits.")
return self[0]
def _readbool(self, start: int, length: int = 0) -> int:
# length is ignored - it's only present to make the function signature consistent.
return self[start]
def _readpad(self, pos, length) -> None:
return None
def _setbin_safe(self, binstring: str, length: None = None, _offset: None = None) -> None:
"""Reset the bitstring to the value given in binstring."""
self._bitstore = bin2bitstore(binstring)
def _setbin_unsafe(self, binstring: str, length: None = None, _offset: None = None) -> None:
"""Same as _setbin_safe, but input isn't sanity checked. binstring mustn't start with '0b'."""
self._bitstore = bin2bitstore_unsafe(binstring)
def _readbin(self, start: int, length: int) -> str:
"""Read bits and interpret as a binary string."""
if length == 0:
return ''
return self._bitstore.getslice(slice(start, start + length, None)).to01()
def _getbin(self) -> str:
"""Return interpretation as a binary string."""
return self._readbin(0, self.len)
def _setoct(self, octstring: str, length: None = None, _offset: None = None) -> None:
"""Reset the bitstring to have the value given in octstring."""
self._bitstore = oct2bitstore(octstring)
def _readoct(self, start: int, length: int) -> str:
"""Read bits and interpret as an octal string."""
if length % 3:
raise InterpretError("Cannot convert to octal unambiguously - not multiple of 3 bits long.")
s = bitarray.util.ba2base(8, self._bitstore.getslice(slice(start, start + length, None)))
return s
def _getoct(self) -> str:
"""Return interpretation as an octal string."""
if self.len % 3:
raise InterpretError("Cannot convert to octal unambiguously - not multiple of 3 bits long.")
ba = self._bitstore.copy() if self._bitstore.modified else self._bitstore
return bitarray.util.ba2base(8, ba)
def _sethex(self, hexstring: str, length: None = None, _offset: None = None) -> None:
"""Reset the bitstring to have the value given in hexstring."""
self._bitstore = hex2bitstore(hexstring)
def _readhex(self, start: int, length: int) -> str:
"""Read bits and interpret as a hex string."""
if length % 4:
raise InterpretError("Cannot convert to hex unambiguously - not a multiple of 4 bits long.")
return bitarray.util.ba2hex(self._bitstore.getslice(slice(start, start + length, None)))
def _gethex(self) -> str:
"""Return the hexadecimal representation as a string.
Raises an InterpretError if the bitstring's length is not a multiple of 4.
"""
if self.len % 4:
raise InterpretError("Cannot convert to hex unambiguously - not a multiple of 4 bits long.")
ba = self._bitstore.copy() if self._bitstore.modified else self._bitstore
return bitarray.util.ba2hex(ba)
def _getlength(self) -> int:
"""Return the length of the bitstring in bits."""
return len(self._bitstore)
def _copy(self: TBits) -> TBits:
"""Create and return a new copy of the Bits (always in memory)."""
# Note that __copy__ may choose to return self if it's immutable. This method always makes a copy.
s_copy = self.__class__()
s_copy._bitstore = self._bitstore.copy()
return s_copy
def _slice(self: TBits, start: int, end: int) -> TBits:
"""Used internally to get a slice, without error checking."""
bs = self.__class__()
bs._bitstore = self._bitstore.getslice(slice(start, end, None))
return bs
def _absolute_slice(self: TBits, start: int, end: int) -> TBits:
"""Used internally to get a slice, without error checking.
Uses MSB0 bit numbering even if LSB0 is set."""
if end == start:
return self.__class__()
assert start < end, f"start={start}, end={end}"
bs = self.__class__()
bs._bitstore = self._bitstore.getslice_msb0(slice(start, end, None))
return bs
def _readtoken(self, name: str, pos: int, length: Optional[int]) -> Tuple[Union[float, int, str, None, Bits], int]:
"""Reads a token from the bitstring and returns the result."""
if length is not None and length > self.length - pos:
raise ReadError("Reading off the end of the data. "
f"Tried to read {length} bits when only {self.length - pos} available.")
if length is None:
meta_type = Bits._register.name_to_meta_dtype[name]
if not meta_type.is_fixed_length and not meta_type.is_unknown_length:
length = len(self) - pos
dtype = Bits._register.get_dtype(name, length)
try:
val = dtype.read_fn(self, pos)
if isinstance(val, tuple):
return val
else:
assert length is not None
return val, pos + length
except KeyError:
raise ValueError(f"Can't parse token {name}:{length}")
def _addright(self, bs: Bits) -> None:
"""Add a bitstring to the RHS of the current bitstring."""
self._bitstore += bs._bitstore
def _addleft(self, bs: Bits) -> None:
"""Prepend a bitstring to the current bitstring."""
if bs._bitstore.immutable:
self._bitstore = bs._bitstore.copy() + self._bitstore
else:
self._bitstore = bs._bitstore + self._bitstore
def _truncateleft(self: TBits, bits: int) -> TBits:
"""Truncate bits from the start of the bitstring. Return the truncated bits."""
assert 0 <= bits <= self.len
if not bits:
return self.__class__()
truncated_bits = self._absolute_slice(0, bits)
if bits == self.len:
self._clear()
return truncated_bits
self._bitstore = self._bitstore.getslice_msb0(slice(bits, None, None))
return truncated_bits
def _truncateright(self: TBits, bits: int) -> TBits:
"""Truncate bits from the end of the bitstring. Return the truncated bits."""
assert 0 <= bits <= self.len
if bits == 0:
return self.__class__()
truncated_bits = self._absolute_slice(self.length - bits, self.length)
if bits == self.len:
self._clear()
return truncated_bits
self._bitstore = self._bitstore.getslice_msb0(slice(None, -bits, None))
return truncated_bits
def _insert(self, bs: Bits, pos: int) -> None:
"""Insert bs at pos."""
assert 0 <= pos <= self.len
self._bitstore[pos: pos] = bs._bitstore
return
def _overwrite(self, bs: Bits, pos: int) -> None:
"""Overwrite with bs at pos."""
assert 0 <= pos <= self.len
if bs is self:
# Just overwriting with self, so do nothing.
assert pos == 0
return
self._bitstore[pos: pos + bs.len] = bs._bitstore
def _delete(self, bits: int, pos: int) -> None:
"""Delete bits at pos."""
assert 0 <= pos <= self.len
assert pos + bits <= self.len, f"pos={pos}, bits={bits}, len={self.len}"
del self._bitstore[pos: pos + bits]
return
def _reversebytes(self, start: int, end: int) -> None:
"""Reverse bytes in-place."""
assert (end - start) % 8 == 0
self._bitstore[start:end] = BitStore(frombytes=self._bitstore.getslice(slice(start, end, None)).tobytes()[::-1])
def _invert(self, pos: int) -> None:
"""Flip bit at pos 1<->0."""
assert 0 <= pos < self.len
self._bitstore.invert(pos)
def _invert_all(self) -> None:
"""Invert every bit."""
self._bitstore.invert()
def _ilshift(self: TBits, n: int) -> TBits:
"""Shift bits by n to the left in place. Return self."""
assert 0 < n <= self.len
self._addright(Bits(n))
self._truncateleft(n)
return self
def _irshift(self: TBits, n: int) -> TBits:
"""Shift bits by n to the right in place. Return self."""
assert 0 < n <= self.len
self._addleft(Bits(n))
self._truncateright(n)
return self
def _imul(self: TBits, n: int) -> TBits:
"""Concatenate n copies of self in place. Return self."""
assert n >= 0
if not n:
self._clear()
return self
m: int = 1
old_len: int = self.len
while m * 2 < n:
self._addright(self)
m *= 2
self._addright(self[0:(n - m) * old_len])
return self
def _readbits(self: TBits, start: int, length: int) -> TBits:
"""Read some bits from the bitstring and return newly constructed bitstring."""
return self._slice(start, start + length)
def _validate_slice(self, start: Optional[int], end: Optional[int]) -> Tuple[int, int]:
"""Validate start and end and return them as positive bit positions."""
if start is None:
start = 0
elif start < 0:
start += self._getlength()
if end is None:
end = self._getlength()
elif end < 0:
end += self._getlength()
if not 0 <= end <= self._getlength():
raise ValueError("end is not a valid position in the bitstring.")
if not 0 <= start <= self._getlength():
raise ValueError("start is not a valid position in the bitstring.")
if end < start:
raise ValueError("end must not be less than start.")
return start, end
def unpack(self, fmt: Union[str, List[Union[str, int]]], **kwargs) -> List[Union[int, float, str, Bits, bool, bytes, None]]:
"""Interpret the whole bitstring using fmt and return list.
fmt -- A single string or a list of strings with comma separated tokens
describing how to interpret the 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.
Raises ValueError if the format is not understood. If not enough bits
are available then all bits to the end of the bitstring will be used.
See the docstring for 'read' for token examples.
"""
return self._readlist(fmt, 0, **kwargs)[0]
def _readlist(self, fmt: Union[str, List[Union[str, int]]], pos: int, **kwargs: int) \
-> Tuple[List[Union[int, float, str, Bits, bool, bytes, None]], int]:
tokens: List[Tuple[str, Optional[Union[str, int]], Optional[str]]] = []
if isinstance(fmt, str):
fmt = [fmt]
keys: Tuple[str, ...] = tuple(sorted(kwargs.keys()))
def convert_length_strings(length_: Optional[Union[str, int]]) -> Optional[int]:
int_length: Optional[int] = None
if isinstance(length_, str):
if length_ in kwargs:
int_length = kwargs[length_]
if name == 'bytes':
int_length *= 8
else:
int_length = length_
return int_length
has_stretchy_token = False
for f_item in fmt:
# Replace integers with 'bits' tokens
if isinstance(f_item, numbers.Integral):
tokens.append(('bits', f_item, None))
else:
stretchy, tkns = tokenparser(f_item, keys)
if stretchy:
if has_stretchy_token:
raise Error("It's not possible to have more than one 'filler' token.")
has_stretchy_token = True
tokens.extend(tkns)
if not has_stretchy_token:
lst = []
for name, length, _ in tokens:
length = convert_length_strings(length)
if name in kwargs and length is None:
# Using default 'bits' - the name is really the length.
value, pos = self._readtoken('bits', pos, kwargs[name])
lst.append(value)
continue
value, pos = self._readtoken(name, pos, length)
if value is not None: # Don't append pad tokens
lst.append(value)
return lst, pos
stretchy_token: Optional[tuple] = None
bits_after_stretchy_token = 0
for token in tokens:
name, length, _ = token
length = convert_length_strings(length)
if stretchy_token:
if name in Bits._register.unknowable_length_names():
raise Error(f"It's not possible to parse a variable length token ('{name}') after a 'filler' token.")
else:
if length is None:
raise Error("It's not possible to have more than one 'filler' token.")
bits_after_stretchy_token += length
if length is None and name not in Bits._register.unknowable_length_names():
assert not stretchy_token
stretchy_token = token
bits_left = self.len - pos
return_values = []
for token in tokens:
name, length, _ = token
if token is stretchy_token:
# Set length to the remaining bits
length = max(bits_left - bits_after_stretchy_token, 0)
length = convert_length_strings(length)
value, newpos = self._readtoken(name, pos, length)
bits_left -= newpos - pos
pos = newpos
if value is not None:
return_values.append(value)
return return_values, pos
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.
>>> BitArray('0xc3e').find('0b1111')
(6,)
"""
bs = Bits._create_from_bitstype(bs)
if len(bs) == 0:
raise ValueError("Cannot find an empty bitstring.")
start, end = self._validate_slice(start, end)
ba = Bits._options.bytealigned if bytealigned is None else bytealigned
p = self._find(bs, start, end, ba)
return p
def _find_lsb0(self, bs: Bits, start: int, end: int, bytealigned: bool) -> Union[Tuple[int], Tuple[()]]:
# A forward find in lsb0 is very like a reverse find in msb0.
assert start <= end
assert Bits._options.lsb0
new_slice = offset_slice_indices_lsb0(slice(start, end, None), len(self), 0)
msb0_start, msb0_end = self._validate_slice(new_slice.start, new_slice.stop)
p = self._rfind_msb0(bs, msb0_start, msb0_end, bytealigned)
if p:
return (self.length - p[0] - bs.length,)
else:
return ()
def _find_msb0(self, bs: Bits, start: int, end: int, bytealigned: bool) -> Union[Tuple[int], Tuple[()]]:
"""Find first occurrence of a binary string."""
while True:
p = self._bitstore.find(bs._bitstore, start, end)
if p == -1:
return ()
if not bytealigned or (p % 8) == 0:
return (p,)
# Advance to just beyond the non-byte-aligned match and try again...
start = p + 1
def findall(self, bs: BitsType, start: Optional[int] = None, end: Optional[int] = None, count: Optional[int] = None,
bytealigned: Optional[bool] = None) -> Iterable[int]:
"""Find all occurrences of bs. Return generator of bit positions.
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).
count -- The maximum number of occurrences to find.
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.
Note that all occurrences of bs are found, even if they overlap.
"""
if count is not None and count < 0:
raise ValueError("In findall, count must be >= 0.")
bs = Bits._create_from_bitstype(bs)
start, end = self._validate_slice(start, end)
ba = Bits._options.bytealigned if bytealigned is None else bytealigned
return self._findall(bs, start, end, count, ba)
def _findall_msb0(self, bs: Bits, start: int, end: int, count: Optional[int],
bytealigned: bool) -> Iterable[int]:
c = 0
for i in self._bitstore.getslice_msb0(slice(start, end, None)).itersearch(bs._bitstore):
if count is not None and c >= count:
return
if bytealigned:
if (start + i) % 8 == 0:
c += 1
yield start + i
else:
c += 1
yield start + i
return
def _findall_lsb0(self, bs: Bits, start: int, end: int, count: Optional[int],
bytealigned: bool) -> Iterable[int]:
assert start <= end
assert Bits._options.lsb0
new_slice = offset_slice_indices_lsb0(slice(start, end, None), len(self), 0)
msb0_start, msb0_end = self._validate_slice(new_slice.start, new_slice.stop)
# Search chunks starting near the end and then moving back.
c = 0
increment = max(8192, bs.len * 80)
buffersize = min(increment + bs.len, msb0_end - msb0_start)
pos = max(msb0_start, msb0_end - buffersize)
while True:
found = list(self._findall_msb0(bs, start=pos, end=pos + buffersize, count=None, bytealigned=False))
if not found:
if pos == msb0_start:
return
pos = max(msb0_start, pos - increment)
continue
while found:
if count is not None and c >= count:
return
c += 1
lsb0_pos = self.len - found.pop() - bs.len
if not bytealigned or lsb0_pos % 8 == 0:
yield lsb0_pos
pos = max(msb0_start, pos - increment)
if pos == msb0_start:
return
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.
"""
bs = Bits._create_from_bitstype(bs)
start, end = self._validate_slice(start, end)
ba = Bits._options.bytealigned if bytealigned is None else bytealigned
if not bs.len:
raise ValueError("Cannot find an empty bitstring.")
p = self._rfind(bs, start, end, ba)
return p
def _rfind_msb0(self, bs: Bits, start: int, end: int, bytealigned: bool) -> Union[Tuple[int], Tuple[()]]:
"""Find final occurrence of a binary string."""
increment = max(4096, len(bs) * 64)
buffersize = increment + len(bs)
p = end
while p > start:
start_pos = max(start, p - buffersize)
ps = list(self._findall_msb0(bs, start_pos, p, count=None, bytealigned=False))
if ps:
while ps:
if not bytealigned or (ps[-1] % 8 == 0):
return (ps[-1],)
ps.pop()
p -= increment
return ()
def _rfind_lsb0(self, bs: Bits, start: int, end: int, bytealigned: bool) -> Union[Tuple[int], Tuple[()]]:
# A reverse find in lsb0 is very like a forward find in msb0.
assert start <= end
assert Bits._options.lsb0
new_slice = offset_slice_indices_lsb0(slice(start, end, None), len(self), 0)
msb0_start, msb0_end = self._validate_slice(new_slice.start, new_slice.stop)
p = self._find_msb0(bs, msb0_start, msb0_end, bytealigned)
if p:
return (self.len - p[0] - bs.length,)
else:
return ()
def cut(self, bits: int, start: Optional[int] = None, end: Optional[int] = None,
count: Optional[int] = None) -> Iterator[Bits]:
"""Return bitstring generator by cutting into bits sized chunks.
bits -- The size in bits of the bitstring chunks to generate.
start -- The bit position to start the first cut. Defaults to 0.
end -- The bit position one past the last bit to use in the cut.
Defaults to len(self).
count -- If specified then at most count items are generated.
Default is to cut as many times as possible.
"""
start_, end_ = self._validate_slice(start, end)
if count is not None and count < 0:
raise ValueError("Cannot cut - count must be >= 0.")
if bits <= 0:
raise ValueError("Cannot cut - bits must be >= 0.")
c = 0
while count is None or c < count:
c += 1
nextchunk = self._slice(start_, min(start_ + bits, end_))
if nextchunk.len == 0:
return
yield nextchunk
if nextchunk._getlength() != bits:
return
start_ += bits
return
def split(self, delimiter: BitsType, start: Optional[int] = None, end: Optional[int] = None,
count: Optional[int] = None, bytealigned: Optional[bool] = None) -> Iterable[Bits]:
"""Return bitstring generator by splitting using a delimiter.
The first item returned is the initial bitstring before the delimiter,
which may be an empty bitstring.
delimiter -- The bitstring used as the divider.
start -- The bit position to start the split. Defaults to 0.
end -- The bit position one past the last bit to use in the split.
Defaults to len(self).
count -- If specified then at most count items are generated.
Default is to split as many times as possible.
bytealigned -- If True splits will only occur on byte boundaries.
Raises ValueError if the delimiter is empty.
"""
delimiter = Bits._create_from_bitstype(delimiter)
if len(delimiter) == 0:
raise ValueError("split delimiter cannot be empty.")
start, end = self._validate_slice(start, end)
bytealigned_: bool = Bits._options.bytealigned if bytealigned is None else bytealigned
if count is not None and count < 0:
raise ValueError("Cannot split - count must be >= 0.")
if count == 0:
return
f = functools.partial(self._find_msb0, bs=delimiter, bytealigned=bytealigned_)
found = f(start=start, end=end)
if not found:
# Initial bits are the whole bitstring being searched
yield self._slice(start, end)
return
# yield the bytes before the first occurrence of the delimiter, even if empty
yield self._slice(start, found[0])
startpos = pos = found[0]
c = 1
while count is None or c < count:
pos += delimiter.len
found = f(start=pos, end=end)
if not found:
# No more occurrences, so return the rest of the bitstring
yield self._slice(startpos, end)
return
c += 1
yield self._slice(startpos, found[0])
startpos = pos = found[0]
# Have generated count bitstrings, so time to quit.
return
def join(self: TBits, sequence: Iterable[Any]) -> TBits:
"""Return concatenation of bitstrings joined by self.
sequence -- A sequence of bitstrings.
"""
s = self.__class__()
if len(self) == 0:
# Optimised version that doesn't need to add self between every item
for item in sequence:
s._addright(Bits._create_from_bitstype(item))
else:
i = iter(sequence)
try:
s._addright(Bits._create_from_bitstype(next(i)))
while True:
n = next(i)
s._addright(self)
s._addright(Bits._create_from_bitstype(n))
except StopIteration:
pass
return s
def tobytes(self) -> bytes:
"""Return the bitstring as bytes, padding with zero bits if needed.
Up to seven zero bits will be added at the end to byte align.
"""
return self._bitstore.tobytes()
def tobitarray(self) -> bitarray.bitarray:
"""Convert the bitstring to a bitarray object."""
if self._bitstore.modified:
# Removes the offset and truncates to length
return bitarray.bitarray(self._bitstore.copy())
else:
return bitarray.bitarray(self._bitstore)
def tofile(self, f: BinaryIO) -> None:
"""Write the bitstring to a file object, padding with zero bits if needed.
Up to seven zero bits will be added at the end to byte align.
"""
# If the bitstring is file based then we don't want to read it all in to memory first.
chunk_size = 8 * 100 * 1024 * 1024 # 100 MiB
for chunk in self.cut(chunk_size):
f.write(chunk.tobytes())
def startswith(self, prefix: BitsType, start: Optional[int] = None, end: Optional[int] = None) -> bool:
"""Return whether the current bitstring starts with prefix.
prefix -- The bitstring to search for.
start -- The bit position to start from. Defaults to 0.
end -- The bit position to end at. Defaults to len(self).
"""
prefix = self._create_from_bitstype(prefix)
start, end = self._validate_slice(start, end)
if end < start + prefix._getlength():
return False
end = start + prefix._getlength()
return self._slice(start, end) == prefix
def endswith(self, suffix: BitsType, start: Optional[int] = None, end: Optional[int] = None) -> bool:
"""Return whether the current bitstring ends with suffix.
suffix -- The bitstring to search for.
start -- The bit position to start from. Defaults to 0.
end -- The bit position to end at. Defaults to len(self).
"""
suffix = self._create_from_bitstype(suffix)
start, end = self._validate_slice(start, end)
if start + suffix.len > end:
return False
start = end - suffix._getlength()
return self._slice(start, end) == suffix
def all(self, value: Any, pos: Optional[Iterable[int]] = None) -> bool:
"""Return True if one or many bits are all set to bool(value).
value -- If value is True then checks for bits set to 1, otherwise
checks for bits set to 0.
pos -- An iterable of bit positions. Negative numbers are treated in
the same way as slice indices. Defaults to the whole bitstring.
"""
value = bool(value)
length = self.len
if pos is None:
if value is True:
return self._bitstore.all_set()
else:
return not self._bitstore.any_set()
for p in pos:
if p < 0:
p += length
if not 0 <= p < length:
raise IndexError(f"Bit position {p} out of range.")
if not bool(self._bitstore.getindex(p)) is value:
return False
return True
def any(self, value: Any, pos: Optional[Iterable[int]] = None) -> bool:
"""Return True if any of one or many bits are set to bool(value).
value -- If value is True then checks for bits set to 1, otherwise
checks for bits set to 0.
pos -- An iterable of bit positions. Negative numbers are treated in
the same way as slice indices. Defaults to the whole bitstring.
"""
value = bool(value)
length = self.len
if pos is None:
if value is True:
return self._bitstore.any_set()
else:
return not self._bitstore.all_set()
for p in pos:
if p < 0:
p += length
if not 0 <= p < length:
raise IndexError(f"Bit position {p} out of range.")
if bool(self._bitstore.getindex(p)) is value:
return True
return False
def count(self, value: Any) -> int:
"""Return count of total number of either zero or one bits.
value -- If bool(value) is True then bits set to 1 are counted, otherwise bits set
to 0 are counted.
>>> Bits('0xef').count(1)
7
"""
# count the number of 1s (from which it's easy to work out the 0s).
count = self._bitstore.count(1)
return count if value else self.len - count
@staticmethod
def _chars_in_pp_token(fmt: str) -> Tuple[str, Optional[int]]:
"""
bin8 -> 'bin', 8
hex12 -> 'hex', 3
o9 -> 'oct', 3
b -> 'bin', None
"""
bpc_dict = {'bin': 1, 'oct': 3, 'hex': 4, 'bytes': 8} # bits represented by each printed character
short_token: Pattern[str] = re.compile(r'(?Pbytes|bin|oct|hex|b|o|h):?(?P\d+)$')
m1 = short_token.match(fmt)
if m1:
length = int(m1.group('len'))
name = m1.group('name')
else:
length = None
name = fmt
aliases = {'hex': 'hex', 'oct': 'oct', 'bin': 'bin', 'bytes': 'bytes', 'b': 'bin', 'o': 'oct', 'h': 'hex'}
try:
name = aliases[name]
except KeyError:
pass # Should be dealt with in the next check
if name not in bpc_dict.keys():
raise ValueError(f"Pretty print formats only support {'/'.join(bpc_dict.keys())}. Received '{fmt}'.")
bpc = bpc_dict[name]
if length is None:
return name, None
if length % bpc != 0:
raise ValueError(f"Bits per group must be a multiple of {bpc} for '{fmt}' format.")
return name, length
@staticmethod
def _format_bits(bits: Bits, chars_per_group: int, bits_per_group: int, sep: str, fmt: str, getter_fn=None) -> str:
if fmt in ['bin', 'oct', 'hex', 'bytes']:
raw = {'bin': bits._getbin,
'oct': bits._getoct,
'hex': bits._gethex,
'bytes': bits._getbytes_printable}[fmt]()
if chars_per_group == 0:
return raw
formatted = sep.join(raw[i: i + chars_per_group] for i in range(0, len(raw), chars_per_group))
return formatted
else:
if fmt == 'bits':
formatted = sep.join(str(getter_fn(b, 0)) for b in bits.cut(bits_per_group))
return formatted
else:
values = []
for i in range(0, len(bits), bits_per_group):
b = bits[i: i + bits_per_group]
values.append(f"{getter_fn(b, 0): >{chars_per_group}}")
formatted = sep.join(values)
return formatted
@staticmethod
def _chars_per_group(bits_per_group: int, fmt: Optional[str]):
# TODO: This method is very fragile, and should use the dtype register.
if fmt is None:
return 0
bpc = {'bin': 1, 'b': 1, 'oct': 3, 'o': 3, 'hex': 4, 'h': 4, 'bytes': 8} # bits represented by each printed character
try:
return bits_per_group // bpc[fmt]
except KeyError:
# Work out how many chars are needed for each format given the number of bits
if fmt in ['u', 'uint', 'uintne', 'uintbe', 'uintle']:
# How many chars is largest uint?
chars_per_value = len(str((1 << bits_per_group) - 1))
elif fmt in ['i', 'int', 'intne', 'intbe', 'intle']:
# Use largest negative int so we get the '-' sign
chars_per_value = len(str((-1 << (bits_per_group - 1))))
elif fmt in ['bfloat', 'bfloatne', 'bfloatbe', 'bfloatle']:
chars_per_value = 23 # Empirical value
elif fmt in ['f', 'float', 'floatne', 'floatbe', 'floatle']:
if bits_per_group in [16, 32]:
chars_per_value = 23 # Empirical value
elif bits_per_group == 64:
chars_per_value = 24 # Empirical value
elif fmt == 'e4m3float':
chars_per_value = 13 # Empirical value
elif fmt == 'e5m2float':
chars_per_value = 19 # Empirical value
elif fmt == 'bool':
chars_per_value = 1 # '0' or '1'
elif fmt == 'bits':
temp = Bits(bits_per_group)
chars_per_value = len(str(temp))
else:
assert False, f"Unsupported format string {fmt}."
raise ValueError(f"Unsupported format string {fmt}.")
return chars_per_value
def _pp(self, name1: str, name2: Optional[str], bits_per_group: int, width: int, sep: str, format_sep: str,
show_offset: bool, stream: TextIO, lsb0: bool, offset_factor: int, getter_fn=None, getter_fn2=None) -> None:
"""Internal pretty print method."""
bpc = {'bin': 1, 'oct': 3, 'hex': 4, 'bytes': 8} # bits represented by each printed character
offset_width = 0
offset_sep = ' :' if lsb0 else ': '
if show_offset:
# This could be 1 too large in some circumstances. Slightly recurrent logic needed to fix it...
offset_width = len(str(len(self))) + len(offset_sep)
if bits_per_group > 0:
group_chars1 = Bits._chars_per_group(bits_per_group, name1)
group_chars2 = Bits._chars_per_group(bits_per_group, name2)
# The number of characters that get added when we add an extra group (after the first one)
total_group_chars = group_chars1 + group_chars2 + len(sep) + len(sep) * bool(group_chars2)
width_excluding_offset_and_final_group = width - offset_width - group_chars1 - group_chars2 - len(
format_sep) * bool(group_chars2)
width_excluding_offset_and_final_group = max(width_excluding_offset_and_final_group, 0)
groups_per_line = 1 + width_excluding_offset_and_final_group // total_group_chars
max_bits_per_line = groups_per_line * bits_per_group # Number of bits represented on each line
else:
assert bits_per_group == 0 # Don't divide into groups
group_chars1 = group_chars2 = 0
width_available = width - offset_width - len(format_sep) * (name2 is not None)
width_available = max(width_available, 1)
if name2 is None:
max_bits_per_line = width_available * bpc[name1]
else:
chars_per_24_bits = 24 // bpc[name1] + 24 // bpc[name2]
max_bits_per_line = 24 * (width_available // chars_per_24_bits)
if max_bits_per_line == 0:
max_bits_per_line = 24 # We can't fit into the width asked for. Show something small.
assert max_bits_per_line > 0
bitpos = 0
first_fb_width = second_fb_width = None
for bits in self.cut(max_bits_per_line):
offset = bitpos // offset_factor
if Bits._options.lsb0:
offset_str = f'{offset_sep}{offset: >{offset_width - len(offset_sep)}}' if show_offset else ''
else:
offset_str = f'{offset: >{offset_width - len(offset_sep)}}{offset_sep}' if show_offset else ''
fb = Bits._format_bits(bits, group_chars1, bits_per_group, sep, name1, getter_fn)
if first_fb_width is None:
first_fb_width = len(fb)
if len(fb) < first_fb_width: # Pad final line with spaces to align it
if Bits._options.lsb0:
fb = ' ' * (first_fb_width - len(fb)) + fb
else:
fb += ' ' * (first_fb_width - len(fb))
fb2 = '' if name2 is None else format_sep + Bits._format_bits(bits, group_chars2, bits_per_group, sep, name2, getter_fn2)
if second_fb_width is None:
second_fb_width = len(fb2)
if len(fb2) < second_fb_width:
if Bits._options.lsb0:
fb2 = ' ' * (second_fb_width - len(fb2)) + fb2
else:
fb2 += ' ' * (second_fb_width - len(fb2))
if Bits._options.lsb0 is True:
line_fmt = fb + fb2 + offset_str + '\n'
else:
line_fmt = offset_str + fb + fb2 + '\n'
stream.write(line_fmt)
bitpos += len(bits)
return
def pp(self, fmt: Optional[str] = None, width: int = 120, sep: str = ' ',
show_offset: bool = True, stream: TextIO = sys.stdout) -> None:
"""Pretty print the bitstring's value.
fmt -- Printed data format. One or two of 'bin', 'oct', 'hex' or 'bytes'.
The number of bits represented in each printed group defaults to 8 for hex and bin,
12 for oct and 32 for bytes. This can be overridden with an explicit length, e.g. 'hex:64'.
Use a length of 0 to not split into groups, e.g. `bin:0`.
width -- Max width of printed lines. Defaults to 120. A single group will always be printed
per line even if it exceeds the max width.
sep -- A separator string to insert between groups. Defaults to a single space.
show_offset -- If True (the default) shows the bit offset in the first column of each line.
stream -- A TextIO object with a write() method. Defaults to sys.stdout.
>>> s.pp('hex16')
>>> s.pp('b, h', sep='_', show_offset=False)
"""
if fmt is None:
fmt = 'bin' if len(self) % 4 != 0 else 'bin, hex'
bpc = {'bin': 1, 'oct': 3, 'hex': 4, 'bytes': 8} # bits represented by each printed character
formats = [f.strip() for f in fmt.split(',')]
if len(formats) == 1:
fmt1, fmt2 = formats[0], None
elif len(formats) == 2:
fmt1, fmt2 = formats[0], formats[1]
else:
raise ValueError(f"Either 1 or 2 comma separated formats must be specified, not {len(formats)}."
" Format string was {fmt}.")
name1, length1 = Bits._chars_in_pp_token(fmt1)
if fmt2 is not None:
name2, length2 = Bits._chars_in_pp_token(fmt2)
if fmt2 is not None and length2 is not None and length1 is not None:
# Both lengths defined so must be equal
if length1 != length2:
raise ValueError(f"Differing bit lengths of {length1} and {length2} in format string '{fmt}'.")
bits_per_group = None
if fmt2 is not None and length2 is not None:
bits_per_group = length2
elif length1 is not None:
bits_per_group = length1
if bits_per_group is None:
if fmt2 is None:
bits_per_group = 8 # Default for 'bin' and 'hex'
if name1 == 'oct':
bits_per_group = 12
elif name1 == 'bytes':
bits_per_group = 32
else:
# Rule of thumb seems to work OK for all combinations.
bits_per_group = 2 * bpc[name1] * bpc[name2]
if bits_per_group >= 24:
bits_per_group //= 2
format_sep = " " # String to insert on each line between multiple formats
self._pp(name1, name2 if fmt2 is not None else None, bits_per_group, width, sep, format_sep, show_offset,
stream, Bits._options.lsb0, 1)
return
def copy(self: TBits) -> TBits:
"""Return a copy of the bitstring."""
return self._copy()
# Create native-endian functions as aliases depending on the byteorder
if byteorder == 'little':
_setfloatne = _setfloatle
_readfloatne = _readfloatle
_getfloatne = _getfloatle
_setbfloatne = _setbfloatle
_readbfloatne = _readbfloatle
_getbfloatne = _getbfloatle
_setuintne = _setuintle
_readuintne = _readuintle
_getuintne = _getuintle
_setintne = _setintle
_readintne = _readintle
_getintne = _getintle
else:
_setfloatne = _setfloatbe
_readfloatne = _readfloatbe
_getfloatne = _getfloatbe
_setbfloatne = _setbfloatbe
_readbfloatne = _readbfloatbe
_getbfloatne = _getbfloatbe
_setuintne = _setuintbe
_readuintne = _readuintbe
_getuintne = _getuintbe
_setintne = _setintbe
_readintne = _readintbe
_getintne = _getintbe
len = length = property(_getlength, doc="The length of the bitstring in bits. Read only.")
bitstring-bitstring-4.1.4/bitstring/bitstore.py 0000664 0000000 0000000 00000014374 14531676336 0021771 0 ustar 00root root 0000000 0000000 from __future__ import annotations
import bitarray
from bitstring.exceptions import CreationError
from typing import Union, Iterable, Optional, overload
def offset_slice_indices_lsb0(key: slice, length: int, offset: 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 - offset
new_stop = length - start - offset
# 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_slice_indices_msb0(key: slice, length: int, offset: int) -> slice:
# First convert slice to all integers
# Length already should take account of the offset
start, stop, step = key.indices(length)
start += offset
stop += offset
# For negative step we sometimes get a negative stop, which can't be used correctly in a new slice
return slice(start, None if stop < 0 else stop, step)
class BitStore(bitarray.bitarray):
"""A light wrapper around bitarray that does the LSB0 stuff"""
__slots__ = ('modified', 'length', 'offset', 'filename', 'immutable')
def __init__(self, *args, immutable: bool = False, frombytes: Optional[Union[bytes, bytearray]] = None,
offset: int = 0, length: Optional[int] = None, filename: str = '',
**kwargs) -> None:
if frombytes is not None:
self.frombytes(frombytes)
self.immutable = immutable
self.offset = offset
self.filename = filename
# Here 'modified' means that it isn't just the underlying bitarray. It could have a different start and end, and be from a file.
# This also means that it shouldn't be changed further, so setting deleting etc. are disallowed.
self.modified = offset != 0 or length is not None or filename != ''
if self.modified:
assert immutable is True
# These class variable only exist if modified is True.
self.length = super().__len__() - self.offset if length is None else length
if self.length < 0:
raise CreationError("Can't create bitstring with a negative length.")
if self.length + self.offset > super().__len__():
self.length = super().__len__() - self.offset
raise CreationError(
f"Can't create bitstring with a length of {self.length} and an offset of {self.offset} from {super().__len__()} bits of data.")
def __new__(cls, *args, **kwargs) -> bitarray.bitarray:
# Just pass on the buffer keyword, not the length, offset, filename and frombytes
new_kwargs = {'buffer': kwargs.get('buffer', None)}
return bitarray.bitarray.__new__(cls, *args, **new_kwargs)
@classmethod
def _create_empty_instance(cls):
return bitarray.bitarray()
def __add__(self, other: bitarray.bitarray) -> BitStore:
assert not self.immutable
return BitStore(super().__add__(other))
def __iter__(self) -> Iterable[bool]:
for i in range(len(self)):
yield self.getindex(i)
def copy(self) -> BitStore:
x = BitStore(self.getslice(slice(None, None, None)))
return x
def __getitem__(self, item: Union[int, slice]) -> Union[int, BitStore]:
# Use getindex or getslice instead
raise NotImplementedError
def getindex_msb0(self, index: int) -> bool:
if self.modified and index >= 0:
index += self.offset
return bool(super().__getitem__(index))
def getslice_msb0(self, key: slice) -> BitStore:
if self.modified:
key = offset_slice_indices_msb0(key, len(self), self.offset)
return BitStore(super().__getitem__(key))
def getindex_lsb0(self, index: int) -> bool:
if self.modified and index >= 0:
index += self.offset
return bool(super().__getitem__(-index - 1))
def getslice_lsb0(self, key: slice) -> BitStore:
if self.modified:
key = offset_slice_indices_lsb0(key, len(self), self.offset)
else:
key = offset_slice_indices_lsb0(key, len(self), 0)
return BitStore(super().__getitem__(key))
@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:
assert not self.immutable
if isinstance(key, slice):
new_slice = offset_slice_indices_lsb0(key, len(self), 0)
super().__setitem__(new_slice, value)
else:
super().__setitem__(-key - 1, value)
def delitem_lsb0(self, key: Union[int, slice]) -> None:
assert not self.immutable
if isinstance(key, slice):
new_slice = offset_slice_indices_lsb0(key, len(self), 0)
super().__delitem__(new_slice)
else:
super().__delitem__(-key - 1)
def invert_msb0(self, index: Optional[int] = None) -> None:
assert not self.immutable
if index is not None:
super().invert(index)
else:
super().invert()
def invert_lsb0(self, index: Optional[int] = None) -> None:
assert not self.immutable
if index is not None:
super().invert(-index - 1)
else:
super().invert()
def any_set(self) -> bool:
if self.modified:
return super().__getitem__(slice(self.offset, self.offset + self.length, None)).any()
else:
return super().any()
def all_set(self) -> bool:
if self.modified:
return super().__getitem__(slice(self.offset, self.offset + self.length, None)).all()
else:
return super().all()
def __len__(self) -> int:
if self.modified:
return self.length
return super().__len__()
setitem_msb0 = bitarray.bitarray.__setitem__
delitem_msb0 = bitarray.bitarray.__delitem__
# Default to the MSB0 methods (mainly to stop mypy from complaining)
getslice = getslice_msb0
getindex = getindex_msb0
__setitem__ = bitarray.bitarray.__setitem__
__delitem__ = bitarray.bitarray.__delitem__ bitstring-bitstring-4.1.4/bitstring/bitstore_helpers.py 0000664 0000000 0000000 00000026025 14531676336 0023507 0 ustar 00root root 0000000 0000000 from __future__ import annotations
import sys
import struct
import functools
from typing import Union, Optional, Dict, Callable
import bitarray
import bitarray.util
from bitstring.utils import tokenparser
from bitstring.exceptions import CreationError, InterpretError
from bitstring.fp8 import e4m3float_fmt, e5m2float_fmt
from bitstring.bitstore import BitStore
byteorder: str = sys.byteorder
# 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:
l = s.split()
except (AttributeError, TypeError):
raise ValueError(f"Expected str object but received a {type(s)} with value {s}.")
return ''.join(l).lower().replace('_', '')
# TODO: Shouldn't this be different for LSB0? The bitstores should be reversed before concatenating and we can raise an error for variable length tokens.
@functools.lru_cache(CACHE_SIZE)
def str_to_bitstore(s: str) -> BitStore:
try:
_, tokens = tokenparser(s)
except ValueError as e:
raise CreationError(*e.args)
bs = BitStore()
if tokens:
bs = bs + bitstore_from_token(*tokens[0])
for token in tokens[1:]:
bs = bs + bitstore_from_token(*token)
bs.immutable = True
return bs
def bin2bitstore(binstring: str) -> BitStore:
binstring = tidy_input_string(binstring)
binstring = binstring.replace('0b', '')
return bin2bitstore_unsafe(binstring)
def bin2bitstore_unsafe(binstring: str) -> BitStore:
try:
return BitStore(binstring)
except ValueError:
raise CreationError(f"Invalid character in bin initialiser {binstring}.")
def hex2bitstore(hexstring: str) -> BitStore:
hexstring = tidy_input_string(hexstring)
hexstring = hexstring.replace('0x', '')
try:
ba = bitarray.util.hex2ba(hexstring)
except ValueError:
raise 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 CreationError("Invalid symbol in oct initialiser.")
return BitStore(ba)
def ue2bitstore(i: Union[str, int]) -> BitStore:
i = int(i)
if i < 0:
raise 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') + uint2bitstore(remainingpart, leadingzeros)
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 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]) -> BitStore:
f = float(f)
try:
b = struct.pack('>f', f)
except OverflowError:
# For consistency we overflow to 'inf'.
b = struct.pack('>f', float('inf') if f > 0 else float('-inf'))
return BitStore(frombytes=b[0:2])
def bfloatle2bitstore(f: Union[str, float]) -> BitStore:
f = float(f)
try:
b = struct.pack(' 0 else float('-inf'))
return BitStore(frombytes=b[2:4])
def e4m3float_2bitstore(f: Union[str, float]) -> BitStore:
f = float(f)
u = e4m3float_fmt.float_to_int8(f)
return uint2bitstore(u, 8)
def e5m2float_2bitstore(f: Union[str, float]) -> BitStore:
f = float(f)
u = e5m2float_fmt.float_to_int8(f)
return uint2bitstore(u, 8)
def uint2bitstore(uint: Union[str, int], length: int) -> BitStore:
uint = int(uint)
try:
if length is None:
raise ValueError("No bit length provided when initialising from unsigned int.")
x = BitStore(bitarray.util.int2ba(uint, length=length, endian='big', signed=False))
except OverflowError as e:
if uint >= (1 << length):
msg = f"{uint} is too large an unsigned integer for a bitstring of length {length}. " \
f"The allowed range is [0, {(1 << length) - 1}]."
raise CreationError(msg)
if uint < 0:
raise CreationError("uint cannot be initialised with a negative number.")
raise e
return x
def int2bitstore(i: Union[str, int], length: int) -> BitStore:
i = int(i)
try:
if length is None:
raise ValueError("No bit length provided when initialising from signed int.")
x = BitStore(bitarray.util.int2ba(i, length=length, endian='big', signed=True))
except OverflowError as e:
if i >= (1 << (length - 1)) or i < -(1 << (length - 1)):
raise 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:
raise e
return x
def uintbe2bitstore(i: Union[str, int], length: int) -> BitStore:
if length % 8 != 0:
raise CreationError(f"Big-endian integers must be whole-byte. Length = {length} bits.")
return uint2bitstore(i, length)
def intbe2bitstore(i: int, length: int) -> BitStore:
if length % 8 != 0:
raise CreationError(f"Big-endian integers must be whole-byte. Length = {length} bits.")
return int2bitstore(i, length)
def uintle2bitstore(i: int, length: int) -> BitStore:
if length % 8 != 0:
raise CreationError(f"Little-endian integers must be whole-byte. Length = {length} bits.")
x = uint2bitstore(i, length).tobytes()
return BitStore(frombytes=x[::-1])
def intle2bitstore(i: int, length: int) -> BitStore:
if length % 8 != 0:
raise CreationError(f"Little-endian integers must be whole-byte. Length = {length} bits.")
x = int2bitstore(i, length).tobytes()
return BitStore(frombytes=x[::-1])
def float2bitstore(f: Union[str, float], length: int) -> BitStore:
f = float(f)
try:
fmt = {16: '>e', 32: '>f', 64: '>d'}[length]
except KeyError:
raise InterpretError(f"Floats can only be 16, 32 or 64 bits long, not {length} bits")
try:
b = struct.pack(fmt, f)
assert len(b) * 8 == length
except (OverflowError, struct.error) as e:
# If float64 doesn't fit it automatically goes to 'inf'. This reproduces that behaviour for other types.
if length in [16, 32]:
b = struct.pack(fmt, float('inf') if f > 0 else float('-inf'))
else:
raise e
return BitStore(frombytes=b)
def floatle2bitstore(f: Union[str, float], length: int) -> BitStore:
f = float(f)
try:
fmt = {16: ' 0 else float('-inf'))
else:
raise e
return BitStore(frombytes=b)
def bytes2bitstore(b: bytes, length: int) -> BitStore:
return BitStore(frombytes=b[:length])
# Create native-endian functions as aliases depending on the byteorder
if byteorder == 'little':
uintne2bitstore = uintle2bitstore
intne2bitstore = intle2bitstore
bfloatne2bitstore = bfloatle2bitstore
floatne2bitstore = floatle2bitstore
else:
uintne2bitstore = uintbe2bitstore
intne2bitstore = intbe2bitstore
bfloatne2bitstore = bfloat2bitstore
floatne2bitstore = float2bitstore
# Given a string of the format 'name=value' get a bitstore representing it by using
# _name2bitstore_func[name](value)
name2bitstore_func: Dict[str, Callable[..., BitStore]] = {
'hex': hex2bitstore,
'h': hex2bitstore,
'0x': hex2bitstore,
'0X': hex2bitstore,
'bin': bin2bitstore,
'b': bin2bitstore,
'0b': bin2bitstore,
'0B': bin2bitstore,
'oct': oct2bitstore,
'o': oct2bitstore,
'0o': oct2bitstore,
'0O': oct2bitstore,
'se': se2bitstore,
'ue': ue2bitstore,
'sie': sie2bitstore,
'uie': uie2bitstore,
'bfloat': bfloat2bitstore,
'bfloatbe': bfloat2bitstore,
'bfloatle': bfloatle2bitstore,
'bfloatne': bfloatne2bitstore,
'e4m3float': e4m3float_2bitstore,
'e5m2float': e5m2float_2bitstore,
}
# Given a string of the format 'name[:]length=value' get a bitstore representing it by using
# _name2bitstore_func_with_length[name](value, length)
name2bitstore_func_with_length: Dict[str, Callable[..., BitStore]] = {
'uint': uint2bitstore,
'int': int2bitstore,
'u': uint2bitstore,
'i': int2bitstore,
'uintbe': uintbe2bitstore,
'intbe': intbe2bitstore,
'uintle': uintle2bitstore,
'intle': intle2bitstore,
'uintne': uintne2bitstore,
'intne': intne2bitstore,
'float': float2bitstore,
'f': float2bitstore,
'floatbe': float2bitstore, # same as 'float'
'floatle': floatle2bitstore,
'floatne': floatne2bitstore,
'bytes': bytes2bitstore
}
def bitstore_from_token(name: str, token_length: Optional[int], value: Optional[str]) -> BitStore:
if token_length == 0:
return BitStore()
# For pad token just return the length in zero bits
if name == 'pad':
bs = BitStore(token_length)
bs.setall(0)
return bs
if value is None:
if token_length is None:
raise ValueError(f"Token has no value ({name}=???).")
else:
raise ValueError(f"Token has no value ({name}:{token_length}=???).")
if name in name2bitstore_func:
bs = name2bitstore_func[name](value)
elif name in name2bitstore_func_with_length:
bs = name2bitstore_func_with_length[name](value, token_length)
elif name == 'bool':
if value in (1, 'True', '1'):
bs = BitStore('1')
elif value in (0, 'False', '0'):
bs = BitStore('0')
else:
raise CreationError("bool token can only be 'True' or 'False'.")
else:
raise CreationError(f"Can't parse token name {name}.")
if token_length is not None and len(bs) != token_length:
raise CreationError(f"Token with length {token_length} packed with value of length {len(bs)} "
f"({name}:{token_length}={value}).")
return bs
bitstring-bitstring-4.1.4/bitstring/bitstream.py 0000664 0000000 0000000 00000071215 14531676336 0022125 0 ustar 00root root 0000000 0000000 from __future__ import annotations
from bitstring.bits import Bits, BitsType
from bitstring.bitarray import BitArray
from bitstring.utils import tokenparser
from bitstring.exceptions import ReadError, ByteAlignError, CreationError, InterpretError
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.
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:
bin -- The bitstring as a binary string.
hex -- The bitstring as a hexadecimal string.
oct -- The bitstring as an octal string.
bytes -- The bitstring as a bytes object.
int -- Interpret as a two's complement signed integer.
uint -- Interpret as a two's complement unsigned integer.
float / floatbe -- Interpret as a big-endian floating point number.
bool -- For single bit bitstrings, interpret as True or False.
se -- Interpret as a signed exponential-Golomb code.
ue -- Interpret as an unsigned exponential-Golomb code.
sie -- Interpret as a signed interleaved exponential-Golomb code.
uie -- Interpret as an unsigned interleaved exponential-Golomb code.
floatle -- Interpret as a little-endian floating point number.
floatne -- Interpret as a native-endian floating point number.
bfloat / bfloatbe -- Interpret as a big-endian 16-bit bfloat type.
bfloatle -- Interpret as a little-endian 16-bit bfloat type.
bfloatne -- Interpret as a native-endian 16-bit bfloat type.
intbe -- Interpret as a big-endian signed integer.
intle -- Interpret as a little-endian signed integer.
intne -- Interpret as a native-endian signed integer.
uintbe -- Interpret as a big-endian unsigned integer.
uintle -- Interpret as a little-endian unsigned integer.
uintne -- Interpret as a native-endian unsigned integer.
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 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 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 > self.len:
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 __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._bitstore.offset, self._bitstore.filename, self._pos)
def overwrite(self, bs: BitsType, pos: Optional[int] = None) -> None:
"""Overwrite with bs 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 not bs.len:
return
if pos is None:
pos = self._pos
if pos < 0:
pos += self._getlength()
if pos < 0 or pos > self.len:
raise ValueError("Overwrite starts outside boundary of bitstring.")
self._overwrite(bs, pos)
self._pos = pos + bs.len
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]) -> 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.
"""
if isinstance(fmt, numbers.Integral):
if fmt < 0:
raise ValueError("Cannot read negative amount.")
if fmt > self.len - self._pos:
raise ReadError(f"Cannot read {fmt} bits, only {self.len - self._pos} available.")
bs = self._slice(self._pos, self._pos + fmt)
self._pos += fmt
return bs
p = self._pos
_, token = tokenparser(fmt)
if len(token) != 1:
self._pos = p
raise ValueError(f"Format string should be a single token, not {len(token)} "
"tokens - use readlist() instead.")
name, length, _ = token[0]
try:
value, self._pos = self._readtoken(name, self._pos, length)
except ValueError as e:
raise InterpretError(e)
return value
def readlist(self, fmt: Union[str, List[Union[int, str]]], **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. An integer is not permitted.
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 ReadError("Substring not found")
self._pos += bs.len
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
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, 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.
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:
bin -- The bitstring as a binary string.
hex -- The bitstring as a hexadecimal string.
oct -- The bitstring as an octal string.
bytes -- The bitstring as a bytes object.
int -- Interpret as a two's complement signed integer.
uint -- Interpret as a two's complement unsigned integer.
float / floatbe -- Interpret as a big-endian floating point number.
bool -- For single bit bitstrings, interpret as True or False.
se -- Interpret as a signed exponential-Golomb code.
ue -- Interpret as an unsigned exponential-Golomb code.
sie -- Interpret as a signed interleaved exponential-Golomb code.
uie -- Interpret as an unsigned interleaved exponential-Golomb code.
floatle -- Interpret as a little-endian floating point number.
floatne -- Interpret as a native-endian floating point number.
bfloat / bfloatbe -- Interpret as a big-endian 16-bit bfloat type.
bfloatle -- Interpret as a little-endian 16-bit bfloat type.
bfloatne -- Interpret as a native-endian 16-bit bfloat type.
intbe -- Interpret as a big-endian signed integer.
intle -- Interpret as a little-endian signed integer.
intne -- Interpret as a native-endian signed integer.
uintbe -- Interpret as a big-endian unsigned integer.
uintle -- Interpret as a little-endian unsigned integer.
uintne -- Interpret as a native-endian unsigned integer.
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 = BitStream()
s_copy._pos = 0
s_copy._bitstore = self._bitstore.copy()
return s_copy
def __iadd__(self, bs: BitsType) -> BitStream:
"""Append bs 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 bs 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 += self._getlength()
if not 0 <= pos <= self._getlength():
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
old = Bits._create_from_bitstype(old)
new = Bits._create_from_bitstype(new)
if not old.len:
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)
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 bitstring-bitstring-4.1.4/bitstring/dtypes.py 0000664 0000000 0000000 00000011027 14531676336 0021436 0 ustar 00root root 0000000 0000000 from __future__ import annotations
import functools
from bitstring.exceptions import InterpretError
from bitstring.bits import Bits
from typing import Optional, Dict, List
class Dtype:
def __init__(self, name: str, length: Optional[int], set_fn, read_fn, get_fn, is_integer, is_float, is_signed,
is_unknown_length, is_fixed_length) -> None:
self.name = name
self.length = length
self.read_fn = functools.partial(read_fn, length=length)
if set_fn is None:
self.set_fn = None
else:
self.set_fn = functools.partial(set_fn, length=length)
self.get_fn = get_fn
self.is_integer = is_integer
self.is_signed = is_signed
self.is_float = is_float
self.is_fixed_length = is_fixed_length
self.is_unknown_length = is_unknown_length
def __str__(self) -> str:
length_str = '' if (self.length == 0 or self.is_fixed_length) else str(self.length)
return f"{self.name}{length_str}"
def __repr__(self) -> str:
s = self.__str__()
return f"{self.__class__.__name__}('{s}')"
class MetaDtype:
# Represents a class of dtypes, such as uint or float, rather than a concrete dtype such as uint8.
def __init__(self, name: str, description: str, set_fn, read_fn, get_fn, is_integer: bool, is_float: bool, is_signed: bool,
is_unknown_length: bool, length: Optional[int] = None):
# Consistency checks
if is_unknown_length and length is not None:
raise ValueError("Can't set is_unknown_length and give a value for length.")
if is_float and is_integer:
raise ValueError("Can't have type that is both float and integer.")
self.name = name
self.description = description
self.is_float = is_float
self.is_integer = is_integer
self.is_signed = is_signed
self.is_fixed_length = length is not None
self.is_unknown_length = is_unknown_length
self.length = length
self.set_fn = set_fn
self.read_fn = read_fn # With a start and usually a length
self.get_fn = get_fn # Interpret everything
def getDtype(self, length: Optional[int] = None) -> Dtype:
if length is None:
if not self.is_fixed_length and not self.is_unknown_length:
raise ValueError(f"No length given for dtype '{self.name}', and meta type is not fixed length.")
d = Dtype(self.name, None, self.set_fn, self.read_fn, self.get_fn, self.is_integer, self.is_float, self.is_signed,
self.is_unknown_length, self.is_fixed_length)
return d
if self.is_unknown_length:
raise ValueError("Length shouldn't be supplied for dtypes that are variable length.")
if self.is_fixed_length:
if length != 0 and length != self.length:
raise ValueError # TODO
length = self.length
d = Dtype(self.name, length, self.set_fn, self.read_fn, self.get_fn, self.is_integer, self.is_float, self.is_signed,
self.is_unknown_length, self.is_fixed_length)
return d
class Register:
_instance: Optional[Register] = None
def __new__(cls) -> Register:
if cls._instance is None:
cls._instance = super(Register, cls).__new__(cls)
cls.name_to_meta_dtype: Dict[str, MetaDtype] = {}
return cls._instance
@classmethod
def add_meta_dtype(cls, meta_dtype: MetaDtype):
cls.name_to_meta_dtype[meta_dtype.name] = meta_dtype
@classmethod
def add_meta_dtype_alias(cls, name: str, alias: str):
cls.name_to_meta_dtype[alias] = cls.name_to_meta_dtype[name]
@classmethod
def get_dtype(cls, name: str, length: Optional[int]) -> Dtype:
try:
meta_type = cls.name_to_meta_dtype[name]
except KeyError:
raise ValueError
d = meta_type.getDtype(length)
# Test if the length makes sense by trying out the getter. # TODO: Optimise!
if length != 0 and not d.is_unknown_length:
temp = Bits(length)
try:
_ = d.read_fn(temp, 0)
except InterpretError as e:
raise ValueError(f"Invalid Dtype: {e.msg}")
return d
# TODO: This should be only calculated if the register has been altered since the last time it was called.
@classmethod
def unknowable_length_names(cls) -> List[str]:
return [dt_name for dt_name in cls.name_to_meta_dtype if cls.name_to_meta_dtype[dt_name].is_unknown_length]
bitstring-bitstring-4.1.4/bitstring/exceptions.py 0000664 0000000 0000000 00000001113 14531676336 0022302 0 ustar 00root root 0000000 0000000
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."""
class InterpretError(Error, ValueError):
"""Inappropriate interpretation of binary data."""
class ByteAlignError(Error):
"""Whole-byte position or length needed."""
class CreationError(Error, ValueError):
"""Inappropriate argument during bitstring creation."""
bitstring-bitstring-4.1.4/bitstring/fp8.py 0000664 0000000 0000000 00000021504 14531676336 0020624 0 ustar 00root root 0000000 0000000 """
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
# When uncompressed this gives the conversion from every possible float16 value to a e4m3float value.
lut_float16_to_e4m3float_compressed = b"x\x01\xed\xdde\xb6\x96\x05\x00E\xe1\x8f\xee\x06\xe9FZA\xa4\xbb\xbb;\xa4SB\xba\xeb\xd2\xdd\x8dt\x97\x92J(\xa14\xa2\x84" \
b"\x92\x8a\xa4\x82\xd2\x1d\x12\x0e\xe2\xfe\xd8k\xf1\xeeg\x06gO\xe0\x84B\xb2\x80\x05,`\x01\x0bX\xc0\x02\x16\xb0\x80\x05,`\x01\x0bX\xc0\x02\x16\xb0\x80\x05\xde" \
b"\xd7\x02\x11d\x01\x0b\x04\xb6@D\x05\xba@$\x05\xba@\xe4\x80\x8b\x12pQ\x03.Z\xc0E\x87\xc5\x80\xc5\x84\xc5\x82\xc5\x86\xc5\x81\xc5\x85\xc5\x83\xc5\x87%\x80%\x84" \
b"%\x82%\x86%\x81}\x00K\nK\x06K\x0eK\x01K\tK\x05K\rK\x03K\x0bK\x07K\x0f\xcb\x00\xcb\x08\xcb\x04\xfb\x10\x96\x19\x96\x05\x96\x15\x96\r\x96\x1d\x96\x03\x96\x13" \
b"\xf6\x11\xeccX.Xn\xd8'\xb0<\xb0Oaya\xf9`\xf9a\x05`\x05a\x85`\x85aE`Ea\xc5`\xc5a%`%a\xa5`\xa5ae`ea\xe5`\xe5a\x15`\x15a\x95`\x95aU`Ua\xd5`\xd5a5`5a\xb5`\xb5au`" \
b"ua\xf5`\xf5a\r`\ra\x8d`\x8daM`\x9f\xc1\x9a\xc2\x9a\xc1\x9a\xc3Z\xc0Z\xc2Z\xc1Z\xc3\xda\xc0\xda\xc2\xda\xc1\xda\xc3:\xc0>\x87u\x84u\x82u\x86u\x81}\x01\xeb\n" \
b"\xeb\x06\xeb\x0e\xeb\x01\xeb\t\xeb\x05\xeb\r\xeb\x03\xeb\x0b\xeb\x07\xeb\x0f\x1b\x00\x1b\x08\x1b\x04\x1b\x0c\x1b\x02\x1b*\x0bX\xc0\x02\x16\xb0\x80\x05,`\x01" \
b"\x0bX\xc0\x02\x16\xb0\x80\x05,`\x01\x0bX\xc0\x02\x16\xb0\x80\x05,`\x01\x0bX\xc0\x02\x16\xb0\x80\x05,\x10\xde\x02a\xb2\x80\x05\x82Z\xe0}\xfd5s\x97\x05,`\x01" \
b"\x0bX\xc0\x02\x16\xb0\x80\x05,`\x01\x0bX\xc0\x02\x16\xb0\x80\x05,\x10\n\r\x93\x05,\x10\xd8\x02\xc3\x15\xe8\x02#\x14\xe8\x02#\x03nT\xc0\x8d\x0e\xb81\x017\x166" \
b"\x0e6\x1e6\x016\x116\t6\x196\x056\x156\r6\x1d6\x036\x136\x0b6\x1b6\x076\x176\x0f6\x1f\xf6%l\x01l!l\x11l1l\tl)l\x19l9l\x05l%l\x15l5l\rl-l\x1dl=l\x03\xec+\xd8" \
b"\xd7\xb0\x8d\xb0M\xb0\xcd\xb0-\xb0\xad\xb0m\xb0o`\xdf\xc2\xb6\xc3v\xc0v\xc2v\xc1\xbe\x83}\x0f\xdb\r\xdb\x03\xdb\x0b\xdb\x07\xfb\x01\xf6#l?\xec\x00\xec \xec" \
b"\x10\xec0\xec\x08\xec(\xec\x18\xec'\xd8q\xd8\xcf\xb0_`'`'a\xa7`\xa7a\xbf\xc2~\x83\x9d\x81\x9d\x85\x9d\x83\x9d\x87]\x80]\x84\xfd\x0e\xfb\x03v\t\xf6'\xec2\xec" \
b"\n\xec*\xec\x1a\xec:\xec\x06\xec/\xd8\xdf\xb0\x9b\xb0[\xb0\x7f`\xff\xc2n\xc3\xee\xc0\xee\xc2\xee\xc1\xee\xc3\x1e\xc0\x1e\xc2\x1e\xc1\x1e\xc3\x9e\xc0\x9e\xc2" \
b"\x9e\xc1\x9e\xc3^\xc0^\xc2^\xc1\xfe\x83\xbd\x86\xbd\x81\xbd\x85\xbd\x93\x05,`\x01\x0bX\xc0\x02\x16\xb0\x80\x05,`\x01\x0bX\xc0\x02\x16\xb0\x80\x05,`\x01\x0bX" \
b"\xc0\x02\x16\xb0\x80\x05,`\x01\x0bX\xc0\x02\x16\x08o\x81\xa0\x1e\x9f\xbb\xdb\x02\x16\x08\xfb\x1f\x0b\xd9\xb3x"
# When uncompressed this gives the conversion from every possible float16 value to a e5m2float value.
lut_float16_to_e5m2float_compressed = b'x\x01\xed\xdde\xa2\x16T\x00E\xd1\x07\n\x82R\x92J\nJHK7"\xa1H#\xd2HIwwI\xa7R\xd2H\xab(\xdd%(\xadt\x89A\xa7\xd2\xdd1\x8c' \
b'\xfbc}k\x06gO\xe0DE\x85\x15-\xb0\xe8\x81\xbd\x12\xd8\xab\x81\xc5\x08,f`\xaf\x05\x16\x0b\x17\x1b\xf7:\xee\r\\\x1c\\\\\\<\\|\\\x02\xdc\x9b\xb8\x84\xb8D\xb8\xc4' \
b'\xb8$\xb8\xa4\xb8d\xb8\xb7po\xe3\x92\xe3R\xe0R\xe2R\xe1R\xe3\xd2\xe0\xde\xc1\xa5\xc5\xa5\xc3\xbd\x8b{\x0f\x97\x1e\x97\x01\x97\x11\x97\t\xf7>.3.\x0b.+.\x1b.;.' \
b'\x07.\'\xee\x03\\.\\n\\\x1e\\^\\>\\~\\\x01\\A\\!\\a\\\x11\\Q\\1\\q\xdc\x87\xb8\x12\xb8\x8fp%q\xa5p\xa5qep\x1f\xe3>\xc1\x95\xc5}\x8a+\x87+\x8f\xab\x80\xab\x88' \
b'\xab\x84\xab\x8c\xab\x82\xab\x8a\xfb\x0cW\r\xf79\xae:\xae\x06\xae&\xae\x16\xae6\xae\x0e\xae.\xae\x1e\xee\x0b\\}\\\x03\\C\\#\\c\xdc\x97\xb8&\xb8\xa6\xb8f\xb8' \
b'\xe6\xb8\x16\xb8\x96\xb8V\xb8\xd6\xb86\xb8\xb6\xb8v\xb8\xf6\xb8\x0e\xb8\x8e\xb8N\xb8\xce\xb8.\xb8\xae\xb8n\xb8\xee\xb8\x1e\xb8\x9e\xb8^\xb8\xde\xb8>\xb8\xbe' \
b'\xb8~\xba\xfe\x11\x91\x02\x91\x02j\x81\xa8\xc0\x06\x04\xf6U`\x03\x03\x1b\x14\xd8\xe0\xc0\x86\x0464\xb0a\xb8\xe1\xb8\x11\xb8\x91\xb8Q\xb8\xd1\xb81\xb8\xafq' \
b'\xdf\xe0\xc6\xe2\xc6\xe1\xc6\xe3&\xe0&\xe2\xbe\xc5M\xc2M\xc6M\xc1M\xc5M\xc3M\xc7\xcd\xc0\xcd\xc4}\x87\x9b\x85\x9b\x8d\x9b\x83\x9b\x8b\x9b\x87\x9b\x8f[\x80' \
b'\xfb\x1e\xf7\x03\xeeG\xdcB\xdcO\xb8\x9fq\x8bp\x8bqKpKq\xcbp\xcbq+p+q\xabp\xabqkpkq\xebp\xebq\x1bp\x1bq\x9bp\xbf\xe06\xe3\xb6\xe0~\xc5\xfd\x86\xdb\x8a\xdb' \
b'\x86\xdb\x8e\xdb\x81\xdb\x89\xdb\x85\xdb\x8d\xfb\x1d\xf7\x07n\x0fn/n\x1fn?\xee\x00\xee \xee\x10\xee0\xee\x08\xee(\xee\x18\xeeO\xdcq\xdc_\xb8\xbfq\xff\xe0\xfe' \
b'\xc5\x9d\xc0\x9d\xc4\x9d\xc2\x9d\xc6\x9d\xc1\x9d\xc5\x9d\xc3\x9d\xc7]\xc0]\xc4]\xc2]\xc6\xfd\x87\xfb\x1fw\x05w\x15w\rw\x1dw\x03w\x13w\x0bw\x1bw\x07w\x17w\x0f' \
b'w\x1f\xf7\x00\xf7\x10\xf7\x08\xf7\x18\xf7\x04\xf7\x14\xf7\x0c\xf7\x1c\xf7B\xa7\x1e\x9fGvG\nD\n\xf4\x7f\tz_,\x0e'
# When uncompressed this maps each single byte integer to the Python float value that it represents when interpreted as a e4m3float value.
lut_int8_to_e4m3float_compressed = b'x\x01\x15\xcc[\xb5\x90!\x10\x80Q"\x18\x81\x08<\xabGQ\x0b\x10\x81\x084\x90\x08D \x02\xcf^\xd1S\xe0\x8f@\x04"\xb8e\xad=/3\x1f!\xfc\x7f\xfd\xad\xf1.\x84Lg\xb29' \
b'\x84\xf7!\xbc!\x92\xc8\x14*\x8d\xce`\xb2\xd8<\x1c.\xe1EO$\x91)T\x1a\x9d\xc1d\xb1y8\\\xc2\x07=\x91D\xa6Pit\x06\x93\xc5\xe6\xe1p\t\x1f\xf5D\x12\x99B\xa5\xd1\x19' \
b'L\x16\x9b\x87\xc3%d=\x91D\xa6Pit\x06\x93\xc5\xe6\xe1p\t\x9f\xf4D\x12\x99B\xa5\xd1\x19L\x16\x9b\x87\xc3%|\xd6\x13Id\n\x95Fg0Yl\x1e\x0e\x97\xf0EO$\x91)T\x1a\xfb' \
b'\xab?\xbe\xb9\xfbnGg\xb29\x84\x1fz"\x89L\xa1\xd2\xe8\x0c&\x8b\xcd\xc3\xe1\x12~\xea\x89$2\x85J\xa33\x98,6\x0f\x87K\xf8\xa5\'\x92\xc8\x14*\x8d\xce`\xb2\xd8<\x1c' \
b'.\xe1\xb7\x9eH"S\xa84:\x83\xc9b\xf3p\xb8\x84\xad\'\x92\xc8\x14*\x8d\xce`\xb2\xd8<\x1c.\xe1\x8f\x9eH"S\xa84:\x83\xc9b\xf3p\xb8\x84\xbfz"\x89L\xa1\xd2\xe8\x0c&\x8b' \
b'\xcd\xc3\xe1\x12^\xf5D\x12\x99B\xa5\xbd\xfe\x03\xc2b\xf2\xc8'
# When uncompressed this maps each single byte integer to the Python float value that it represents when interpreted as a e5m2float value.
lut_int8_to_e5m2float_compressed = b'x\x01\x1d\xca\xd9\x11\x10\x06\x08EQJ\xb1\x0b\x8d[\xd0\xb8\xb4A\'R\n]\xc45\xc1\xb8\xb5A)\x9e\t3\xe7\x87\xfb"\xfe\xbf\x87\x11\xcd\x12\x8f"\x1e\x90\x14\xcd\xb0\x1c' \
b'\xf1\x87NR4\xc3r\xc4c\x9d\xa4h\x86\xe5\x88\':I\xd1\x0c\xcb\x11Ou\x92\xa2\x19\x96#\x9e\xe9$E3,G<\xd7I\x8afX\x8e\xf8S\')\x9aa9"u\x92\xa2\x19\x96#^\xe8$E3,G\xbc\xd4' \
b'I\x8afX\x8e\xf8K\')\x9aa9\xe2\x95NR4\xc3r\xc4k\x9d\xa4h\x86\xe5\x887:I\xd1\x0c\xcb\x11ou\x92b\xdf\xf9\xfdm\xc7\x12\xefu\x92\xa2\x19\x96#>\xe8$E3,G|\xd4I\x8afX' \
b'\x8e\xf8\xa4\x93\x14\xcd\xb0\x1c\xf1Y\')\x9aa9\xe2\x8bNR4\xc3r\xc4?:I\xd1\x0c\xcb\x11\xff\xea$E3,G\xacNR4\xc3r\xc4W\x9d\xa4h\x86\xe5\x88\xfft\x92\xa2\x19\x96#' \
b'\xbe\xe9$E3,G|\xd7I\x8afX\x8e\xf8\xa1\x93\x14\xcd\xb0\x1c\xf1S\')\x9aa9\xe2\x97NR\xbf~\x03\x96j\xecR'
class FP8Format:
"""Defining an 8-bit floating point format"""
def __init__(self, exp_bits: int, bias: int):
# We use look up tables to go from an IEEE float16 to the best float8 representation.
# For startup efficiency they've been precalculated and zipped up
if exp_bits == 4 and bias == 8:
self.lut_float16_to_float8 = zlib.decompress(lut_float16_to_e4m3float_compressed)
self.lut_int8_to_float = struct.unpack('<256f', zlib.decompress(lut_int8_to_e4m3float_compressed))
elif exp_bits == 5 and bias == 16:
self.lut_float16_to_float8 = zlib.decompress(lut_float16_to_e5m2float_compressed)
self.lut_int8_to_float = struct.unpack('<256f', zlib.decompress(lut_int8_to_e5m2float_compressed))
else: # pragma: no cover
raise RuntimeError("Unsupported float8 format trying to be created. Only e4m3float and e5m2float available for now.")
# # This is how the LUTs above were calculated. For reference only - shouldn't be needed any more
# self.lut_int8_to_float = self.createLUT_for_int8_to_float()
# self.lut_float16_to_float8 = self.createLUT_for_float16_to_float8()
# # Then we used a line like this to create the constants:
# lut_float16_to_e4m3float_compressed = zlib.compress(self.lut_float16_to_float8, 1)
# # See also the test_fp8.py unit tests which check the equivalence.
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 0b01111111 if f > 0 else 0b11111111
f16_int = int.from_bytes(b, byteorder='big')
# Then use this as an index to our large LUT
return self.lut_float16_to_float8[f16_int]
# We create the 1.5.2 and 1.4.3 formats. The proposed 1.3.4 format isn't supported here.
e4m3float_fmt = FP8Format(exp_bits=4, bias=8)
e5m2float_fmt = FP8Format(exp_bits=5, bias=16)
bitstring-bitstring-4.1.4/bitstring/methods.py 0000664 0000000 0000000 00000011140 14531676336 0021565 0 ustar 00root root 0000000 0000000 from __future__ import annotations
from bitstring.bits import Bits
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, name2bitstore_func_with_length
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.
if value in kwargs:
value = kwargs[value]
# If the length is in the kwd dictionary then use that too.
if length in kwargs:
length = kwargs[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 = 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 Bits._options.lsb0:
for name, _, _ in tokens:
if name in Bits._register.unknowable_length_names():
raise CreationError(f"Variable length tokens ('{name}') cannot be used in lsb0 mode.")
for b in bsl[::-1]:
s._bitstore += b
else:
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.")
bitstring-bitstring-4.1.4/bitstring/options.py 0000664 0000000 0000000 00000004765 14531676336 0021634 0 ustar 00root root 0000000 0000000 from __future__ import annotations
from bitstring.bits import Bits
from bitstring.bitarray import BitArray
from bitstring.bitstore import BitStore
class Options:
"""Internal class to create singleton module options instance."""
_instance = None
def __init__(self):
self.set_lsb0(False)
self._bytealigned = False
@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)
if self._lsb0:
Bits._find = Bits._find_lsb0 # type: ignore
Bits._rfind = Bits._rfind_lsb0 # type: ignore
Bits._findall = Bits._findall_lsb0 # type: ignore
BitArray._ror = BitArray._rol_msb0 # type: ignore
BitArray._rol = BitArray._ror_msb0 # type: ignore
BitArray._append = BitArray._append_lsb0 # type: ignore
# An LSB0 prepend is an MSB0 append
BitArray._prepend = BitArray._append_msb0 # type: ignore
BitStore.__setitem__ = BitStore.setitem_lsb0 # type: ignore
BitStore.__delitem__ = BitStore.delitem_lsb0 # type: ignore
BitStore.getindex = BitStore.getindex_lsb0
BitStore.getslice = BitStore.getslice_lsb0
BitStore.invert = BitStore.invert_lsb0 # type: ignore
else:
Bits._find = Bits._find_msb0 # type: ignore
Bits._rfind = Bits._rfind_msb0 # type: ignore
Bits._findall = Bits._findall_msb0 # type: ignore
BitArray._ror = BitArray._ror_msb0 # type: ignore
BitArray._rol = BitArray._rol_msb0 # type: ignore
BitArray._append = BitArray._append_msb0 # type: ignore
BitArray._prepend = BitArray._append_lsb0 # type: ignore
BitStore.__setitem__ = BitStore.setitem_msb0 # type: ignore
BitStore.__delitem__ = BitStore.delitem_msb0 # type: ignore
BitStore.getindex = BitStore.getindex_msb0
BitStore.getslice = BitStore.getslice_msb0
BitStore.invert = BitStore.invert_msb0 # type: ignore
@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
bitstring-bitstring-4.1.4/bitstring/py.typed 0000664 0000000 0000000 00000000000 14531676336 0021240 0 ustar 00root root 0000000 0000000 bitstring-bitstring-4.1.4/bitstring/utils.py 0000664 0000000 0000000 00000025312 14531676336 0021270 0 ustar 00root root 0000000 0000000 from __future__ import annotations
import itertools
import functools
import re
from typing import Tuple, List, Optional, Pattern, Dict, Union, Match
import sys
from bitstring.exceptions import Error
byteorder: str = sys.byteorder
TOKEN_RE: Pattern[str] = None
# A token name followed by optional : then an integer number
TOKEN_INT_RE: Pattern[str] = None
# Tokens which have an unknowable (in advance) length, so it must not be supplied.
UNKNOWABLE_LENGTH_TOKENS: List[str] = None
def initialise_constants(init_names: List[str], unknowable_length_names: List[str]) -> None:
global TOKEN_RE, TOKEN_INT_RE, UNKNOWABLE_LENGTH_TOKENS
init_names.sort(key=len, reverse=True)
TOKEN_RE = re.compile(r'^(?P' + '|'.join(init_names) + r'):?(?P[^=]+)?(=(?P.*))?$', re.IGNORECASE)
TOKEN_INT_RE = re.compile(r'^(?P' + '|'.join(init_names) + r'):?(?P\d*)$')
UNKNOWABLE_LENGTH_TOKENS = unknowable_length_names
CACHE_SIZE = 256
DEFAULT_BITS: Pattern[str] = re.compile(r'^(?P[^=]+)?(=(?P.*))?$', re.IGNORECASE)
MULTIPLICATIVE_RE: Pattern[str] = re.compile(r'^(?P.*)\*(?P.+)')
# Hex, oct or binary literals
LITERAL_RE: Pattern[str] = re.compile(r'^(?P0([xob]))(?P.+)', re.IGNORECASE)
# An endianness indicator followed by one or more struct.pack codes
STRUCT_PACK_RE: Pattern[str] = re.compile(r'^(?P[<>@=]){1}(?P(?:\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[<>@=])?(?P(?:\d*[bBhHlLqQefd])+)$')
# An endianness indicator followed by exactly one struct.pack codes
SINGLE_STRUCT_PACK_RE: Pattern[str] = re.compile(r'^(?P[<>@=]){1}(?P(?:[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': 'int:8', 'B': 'uint:8',
'h': 'intbe:16', 'H': 'uintbe:16',
'l': 'intbe:32', 'L': 'uintbe:32',
'q': 'intbe:64', 'Q': 'uintbe:64',
'e': 'floatbe:16', 'f': 'floatbe:32', 'd': 'floatbe:64'}
# Little-endian
REPLACEMENTS_LE: Dict[str, str] = {'b': 'int:8', 'B': 'uint:8',
'h': 'intle:16', 'H': 'uintle:16',
'l': 'intle:32', 'L': 'uintle:32',
'q': 'intle:64', 'Q': 'uintle:64',
'e': 'floatle:16', 'f': 'floatle:32', 'd': 'floatle:64'}
# Native-endian
REPLACEMENTS_NE: Dict[str, str] = {'b': 'int:8', 'B': 'uint:8',
'h': 'intne:16', 'H': 'uintne:16',
'l': 'intne:32', 'L': 'uintne:32',
'q': 'intne:64', 'Q': 'uintne:64',
'e': 'floatne:16', 'f': 'floatne:32', 'd': 'floatne:64'}
# Tokens which are always the same length, so it doesn't need to be supplied.
ALWAYS_FIXED_LENGTH_TOKENS: Dict[str, int] = {'bool': 1,
'bfloat': 16,
'e4m3float': 8,
'e5m2float': 8}
# 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) -> Tuple[str, int]:
# Any single token with just a name and length
m = SINGLE_STRUCT_PACK_RE.match(fmt)
if m:
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]
m2 = TOKEN_INT_RE.match(fmt)
if m2:
name = m2.group('name')
length_str = m2.group('length')
length = 0 if length_str == '' else int(length_str)
else:
raise ValueError(f"Can't parse 'name[:]length' token '{fmt}'.")
if name in UNKNOWABLE_LENGTH_TOKENS:
if length is not None:
raise ValueError(
f"The token '{name}' has a variable length and can't be given the fixed length of {length}.")
if name in ALWAYS_FIXED_LENGTH_TOKENS.keys():
token_length = ALWAYS_FIXED_LENGTH_TOKENS[name]
if length not in [0, token_length]:
raise ValueError(f"{name} tokens can only be {token_length} bits long, not {length} bits.")
length = token_length
if length is None:
length = 0
return name, length
@functools.lru_cache(CACHE_SIZE)
def parse_single_token(token: str) -> Tuple[str, str, Optional[str]]:
m1 = TOKEN_RE.match(token)
if m1:
name = m1.group('name')
length = m1.group('len')
value = m1.group('value')
else:
# If you don't specify a 'name' then the default is 'bits':
name = 'bits'
m2 = DEFAULT_BITS.match(token)
if not m2:
raise ValueError(f"Don't understand token '{token}'.")
length = m2.group('len')
value = m2.group('value')
if name in ALWAYS_FIXED_LENGTH_TOKENS.keys():
token_length = str(ALWAYS_FIXED_LENGTH_TOKENS[name])
if length is not None and length != token_length:
raise ValueError(f"{name} tokens can only be {token_length} bits long, not {length} bits.")
length = token_length
return name, length, value
@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]
"""
# Remove whitespace
fmt = ''.join(fmt.split())
# Expand any brackets.
fmt = expand_brackets(fmt)
# 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(',')]
return_values: List[Tuple[str, Union[int, str, None], Optional[str]]] = []
stretchy_token = False
for meta_token in meta_tokens:
# See if it has a multiplicative factor
m = MULTIPLICATIVE_RE.match(meta_token)
if not m:
factor = 1
else:
factor = int(m.group('factor'))
meta_token = m.group('token')
# See if it's a struct-like format
m = STRUCT_PACK_RE.match(meta_token)
if m:
tokens = structparser(m)
else:
tokens = [meta_token]
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...
m = LITERAL_RE.match(token)
if m:
ret_vals.append((m.group('name'), None, m.group('value')))
continue
name, length, value = parse_single_token(token)
if name in UNKNOWABLE_LENGTH_TOKENS:
if length is not None:
raise ValueError(f"The token '{name}' has a variable length and can't be given the fixed length of {length}.")
else:
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)
if length < 0:
raise Error
# For the 'bytes' token convert length to bits.
if name == 'bytes':
length *= 8
except Error:
raise ValueError("Can't read a token with a negative 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_values.extend(itertools.repeat(ret_vals, factor))
return_values = itertools.chain.from_iterable(return_values)
return stretchy_token, list(return_values)
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):
if s[p] == '(':
count += 1
if s[p] == ')':
count -= 1
if not count:
break
p += 1
if count:
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*(
bracket_re = re.compile(r'(?P\d+)\*\(')
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
bitstring-bitstring-4.1.4/doc/ 0000775 0000000 0000000 00000000000 14531676336 0016313 5 ustar 00root root 0000000 0000000 bitstring-bitstring-4.1.4/doc/_static/ 0000775 0000000 0000000 00000000000 14531676336 0017741 5 ustar 00root root 0000000 0000000 bitstring-bitstring-4.1.4/doc/_static/custom.css 0000664 0000000 0000000 00000003134 14531676336 0021766 0 ustar 00root root 0000000 0000000
:root {
--mainNavColor: #348ad1;
--sidebarColor: #f8f8ff;
--inlineCodeBackgroundColor: rgb(0 0 0 / 0%);
}
div#top_nav nav {
background-image: linear-gradient(#348ad1, #046ab1);
padding: 0.2rem 1rem;
}
div#top_nav nav h1 img{
height: 3rem;
}
.highlight {
background: #f8f8f8;
}
div.document {
margin-bottom: 1rem;
}
div.document div.documentwrapper {
max-width: 55rem;
}
div.document div.highlight {
border-radius: 0.0rem;
border-left: 3px dotted rgba(128, 128, 128, 40%);
background-color: var(--mainBackgroundColor);
}
h1, h2, h3 {
color: #046ab1;
}
div.document div.admonition.attention, div.document div.admonition.caution, div.document div.admonition.warning {
border: 2px solid #d77732;
}
div.document div.admonition.attention p.admonition-title, div.document div.admonition.caution p.admonition-title, div.document div.admonition.warning p.admonition-title {
color: #ffffff;
background-color: #d77732;
}
div.document div.admonition.attention, div.document div.admonition.caution, div.document div.admonition.note {
border: 2px solid #587f9f;
}
div.document div.admonition.attention p.admonition-title, div.document div.admonition.caution p.admonition-title, div.document div.admonition.note p.admonition-title {
color: #ffffff;
background-color: #587f9f;
}
div.document li p {
margin: 0 0 0.3rem 0;
}
div.document table.docutils td {
border: none;
padding: 0.1rem 1rem;
}
div.document table.docutils th {
border: none;
padding: 0.1rem 1rem;
}
div.document hr {
height: 0.25rem;
background-color: #d03434;
} bitstring-bitstring-4.1.4/doc/appendices.rst 0000664 0000000 0000000 00000000611 14531676336 0021156 0 ustar 00root root 0000000 0000000 .. currentmodule:: bitstring
###########
Appendices
###########
Gathered together here are a few odds and ends that didn't fit well into either the user manual or the reference section. The only unifying theme is that none of them provide any vital knowledge about :mod:`bitstring`, and so they can all be safely ignored.
.. toctree::
:maxdepth: 2
exp-golomb
fp8
optimisation
bitstring-bitstring-4.1.4/doc/array.rst 0000664 0000000 0000000 00000047511 14531676336 0020173 0 ustar 00root root 0000000 0000000 .. currentmodule:: bitstring
.. note::
The Array class is new in version 4.1 of bitstring, and is considered a 'beta' feature for now.
There may be some small changes in future point releases and it hasn't been tested as well as the rest of the library.
This documentation may also be a bit 'beta'.
Array
=====
.. class:: Array(dtype: str, initializer: Iterable | int | Array | array.array | Bits | bytes | bytearray | memoryview | BinaryIO | None = None, trailing_bits: BitsType | None = None)
Create a new ``Array`` whose elements are set by the `dtype` (data-type) string.
This can be any format which has a fixed length.
See :ref:`format_tokens` and :ref:`compact_format` for details on allowed dtype strings, noting that only formats with well defined bit lengths are allowed.
The ``Array`` class is a way to efficiently store data that has a single type with a set length.
The ``bitstring.Array`` type is meant as a more flexible version of the standard ``array.array``, and can be used the same way. ::
import array
import bitstring
x = array.array('f', [1.0, 2.0, 3.14])
y = bitstring.Array('=f', [1.0, 2.0, 3.14])
assert x.tobytes() == y.tobytes()
This example packs three 32-bit floats into objects using both libraries.
The only difference is the explicit native endianness for the format string of the bitstring version.
The bitstring Array's advantage lies in the way that any fixed-length bitstring format can be used instead of just the dozen or so typecodes supported by the ``array`` module.
For example ``'uint4'``, ``'bfloat'`` or ``'hex12'`` can be used, and the endianness of multi-byte dtypes can be properly specified.
Each element in the ``Array`` must then be something that makes sense for the ``dtype``.
Some examples will help illustrate::
from bitstring import Array
# Each unsigned int is stored in 4 bits
a = Array('uint4', [0, 5, 5, 3, 2])
# Convert and store floats in 8 bits each
b = Array('e5m2float', [-56.0, 0.123, 99.6])
# Each element is a 7 bit signed integer
c = Array('int7', [-3, 0, 120])
You can then access and modify the ``Array`` with the usual notation::
a[1:4] # Array('uint4', [5, 5, 3])
b[0] # -56.0
c[-1] # 120
a[0] = 2
b.extend([0.0, -1.5])
Conversion between ``Array`` types can be done using the :meth:`astype` method.
If elements of the old array don't fit or don't make sense in the new array then the relevant exceptions will be raised. ::
>>> x = Array('float64', [89.3, 1e34, -0.00000001, 34])
>>> y = x.astype('float16')
>>> y
Array('float16', [89.3125, inf, -0.0, 34.0])
>>> y = y.astype('e4m3float')
>>> y
Array('e4m3float', [88.0, 240.0, 0.0, 32.0])
>>> y.astype('uint8')
Array('uint8', [88, 240, 0, 32])
>>> y.astype('uint7')
bitstring.CreationError: 240 is too large an unsigned integer for a bitstring of length 7. The allowed range is [0, 127].
You can also reinterpret the data by changing the :attr:`dtype` property directly.
This will not copy any data but will cause the current data to be shown differently. ::
>>> x = Array('int16', [-5, 100, -4])
>>> x
Array('int16', [-5, 100, -4])
>>> x.dtype = 'int8'
>>> x
Array('int8', [-1, -5, 0, 100, -1, -4])
The data for the array is stored internally as a :class:`BitArray` object.
It can be directly accessed using the :attr:`data` property.
You can freely manipulate the internal data using all of the methods available for the :class:`BitArray` class.
The :class:`Array` object also has a :attr:`trailing_bits` read-only data member, which consists of the end bits of the :attr:`data` that are left over when the :class:`Array` is interpreted using the :attr:`dtype`.
Typically :attr:`trailing_bits` will be an empty :class:`BitArray` but if you change the length of the :attr:`data` or change the :attr:`dtype` specification there may be some bits left over.
Some methods, such as :meth:`~Array.append` and :meth:`~Array.extend` will raise an exception if used when :attr:`trailing_bits` is not empty, as it not clear how these should behave in this case. You can however still use :meth:`~Array.insert` which will always leave the :attr:`trailing_bits` unchanged.
The :attr:`dtype` string can be a type code such as ``'>H'`` or ``'=d'`` but it can also be a string defining any format which has a fixed-length in bits, for example ``'int12'``, ``'bfloat'``, ``'bytes5'`` or ``'bool'``.
Note that the typecodes must include an endianness character to give the byte ordering.
This is more like the ``struct`` module typecodes, and is different to the ``array.array`` typecodes which are always native-endian.
The correspondence between the big-endian type codes and bitstring dtype strings is given in the table below.
========= ===================
Type code bitstring dtype
========= ===================
``'>b'`` ``'int8'``
``'>B'`` ``'uint8'``
``'>h'`` ``'int16'``
``'>H'`` ``'uint16'``
``'>l'`` ``'int32'``
``'>L'`` ``'uint32'``
``'>q'`` ``'int64'``
``'>Q'`` ``'uint64'``
``'>e'`` ``'float16'``
``'>f'`` ``'float32'``
``'>d'`` ``'float64'``
========= ===================
The endianness character can be ``'>'`` for big-endian, ``'<'`` for little-endian or ``'='`` for native-endian (``'@'`` can also be used for native-endian).
In the bitstring dtypes the default is big-endian, but you can specify little or native endian using ``'le'`` or ``'ne'`` modifiers, for example:
============ =============================
Type code bitstring dtype
============ =============================
``'>H'`` ``'uint16'`` / ``'uintbe16'``
``'=H'`` ``'uintne16'``
``' None
Add a new element with value `x` to the end of the Array.
The type of `x` should be appropriate for the type of the Array.
Raises a ``ValueError`` if the Array's bit length is not a multiple of its dtype length (see :attr:`~Array.trailing_bits`).
.. method:: Array.astype(dtype: str) -> Array
Cast the ``Array`` to the new `dtype` and return the result. ::
>>> a = Array('float64', [-990, 34, 1, 0.25])
>>> a.data
BitArray('0xc08ef0000000000040410000000000003ff00000000000003fd0000000000000')
>>> b = a.astype('float16')
>>> b.data
BitArray('0xe3bc50403c003400')
>>> a == b
Array('bool', [True, True, True, True])
.. method:: Array.byteswap() -> None
Change the byte endianness of each element.
Raises a ``ValueError`` if the format is not an integer number of bytes long. ::
>>> a = Array('uint32', [100, 1, 999])
>>> a.byteswap()
>>> a
Array('uint32', [1677721600, 16777216, 3875733504])
>>> a.dtype = 'uintle32'
>>> a
Array('uintle32', [100, 1, 999])
.. method:: Array.count(value: float | int | str | bytes) -> int
Returns the number of elements set to *value*. ::
>>> a = Array('hex4')
>>> a.data += '0xdeadbeef'
>>> a
Array('hex4', ['d', 'e', 'a', 'd', 'b', 'e', 'e', 'f'])
>>> a.count('e')
3
For floating point types using a `value` of ``float('nan')`` will count the number of elements for which ``math.isnan()`` returns ``True``.
.. method:: Array.equals(other: Any) -> bool
Equality test - `other` can be either another bitstring Array or an ``array``.
Returns ``True`` if the dtypes are equivalent and the underlying bit data is the same, otherwise returns ``False``. ::
>>> a = Array('u8', [1, 2, 3, 2, 1])
>>> a[0:3].equals(a[-1:-4:-1])
True
>>> b = Array('i8', [1, 2, 3, 2, 1])
>>> a.equals(b)
False
To compare only the values contained in the Array, extract them using :meth:`~Array.tolist` first::
>>> a.tolist() == b.tolist()
True
Note that the ``==`` operator will perform an element-wise equality check and return a new ``Array`` of dtype ``'bool'`` (or raise an exception).
>>> a == b
Array('bool', [True, True, True, True, True])
.. method:: Array.extend(iterable: Iterable | Array) -> None
Extend the Array by constructing new elements from the values in a list or other iterable.
The `iterable` can be another ``Array`` or an ``array.array``, but only if the dtype is the same. ::
>>> a = Array('int5', [-5, 0, 10])
>>> a.extend([3, 2, 1])
>>> a.extend(a[0:3] // 5)
>>> a
Array('int5', [-5, 0, 10, 3, 2, 1, -1, 0, 2])
.. method:: Array.fromfile(f: BinaryIO, n: int | None = None) -> None
Append items read from a file object.
.. method:: Array.insert(i: int, x: float | int | str | bytes) -> None
Insert an item at a given position. ::
>>> a = Array('e5m2float', [-10, -5, -0.5, 5, 10])
>>> a.insert(3, 0.5)
>>> a
Array('e5m2float', [-10.0, -5.0, -0.5, 0.5, 5.0, 10.0])
.. method:: Array.pop(i: int | None = None) -> float | int | str | bytes
Remove and return the item at position i.
If a position isn't specified the final item is returned and removed. ::
>>> Array('bytes3', [b'ABC', b'DEF', b'ZZZ'])
>>> a.pop(0)
b'ABC'
>>> a.pop()
b'ZZZ'
>>> a.pop()
b'DEF'
.. method:: Array.pp(fmt: str | None = None, width: int = 120, show_offset: bool = False, stream: TextIO = sys.stdout) -> None
Pretty print the Array.
The format string `fmt` defaults to the Array's current :attr:`dtype`, but any other valid Array format string can be used.
If a `fmt` doesn't have an explicit length, the Array's :attr:`itemsize` will be used.
A pair of comma-separated format strings can also be used - if both formats specify a length they must be the same. For example ``'float, hex16'`` or ``'u4, b4'``.
The output will try to stay within `width` characters per line, but will always output at least one element value.
Setting `show_offset` to ``True`` will add a element index to each line of the output.
An output `stream` can be specified. This should be an object with a ``write`` method and the default is ``sys.stdout``.
>>> a = Array('u20', bytearray(range(100)))
>>> a.pp(width=70)
[
16 131844 20576 460809 41136 789774 61697 70163
82257 399128 102817 728093 123378 8482 143938 337447
164498 666412 185058 995377 205619 275766 226179 604731
246739 933696 267300 214085 287860 543050 308420 872015
328981 152404 349541 481369 370101 810334 390662 90723
]
>>> a.pp('hex32', show_offset=True, width=70)
[
0: 00010203 04050607 08090a0b 0c0d0e0f 10111213 14151617 18191a1b
7: 1c1d1e1f 20212223 24252627 28292a2b 2c2d2e2f 30313233 34353637
14: 38393a3b 3c3d3e3f 40414243 44454647 48494a4b 4c4d4e4f 50515253
21: 54555657 58595a5b 5c5d5e5f 60616263
]
>>> a.pp('i12, hex', width=70)
[
0 258 48 1029 96 1800 : 000 102 030 405 060 708
144 -1525 192 -754 241 17 : 090 a0b 0c0 d0e 0f1 011
289 788 337 1559 385 -1766 : 121 314 151 617 181 91a
433 -995 481 -224 530 547 : 1b1 c1d 1e1 f20 212 223
578 1318 626 -2007 674 -1236 : 242 526 272 829 2a2 b2c
722 -465 771 306 819 1077 : 2d2 e2f 303 132 333 435
867 1848 915 -1477 963 -706 : 363 738 393 a3b 3c3 d3e
1012 65 1060 836 1108 1607 : 3f4 041 424 344 454 647
1156 -1718 1204 -947 1252 -176 : 484 94a 4b4 c4d 4e4 f50
1301 595 1349 1366 1397 -1959 : 515 253 545 556 575 859
1445 -1188 1493 -417 1542 354 : 5a5 b5c 5d5 e5f 606 162
] + trailing_bits = 0x63
.. method:: Array.reverse() -> None
Reverse the order of all items in the Array. ::
>>> a = Array('>L', [100, 200, 300])
>>> a.reverse()
>>> a
Array('>L', [300, 200, 100])
.. method:: Array.tobytes() -> bytes
Return Array data as bytes object, padding with zero bits at the end if needed. ::
>>> a = Array('i4', [3, -6, 2, -3, 2, -7])
>>> a.tobytes()
b':-)'
.. method:: Array.tofile(f: BinaryIO) -> None
Write Array data to a file, padding with zero bits at the end if needed.
.. method:: Array.tolist() -> List[float | int | str | bytes]
Return Array items as a list.
Each packed element of the Array is converted to an ordinary Python object such as a ``float`` or an ``int`` depending on the Array's format, and returned in a Python list.
Special Methods
---------------
Type promotion
""""""""""""""
Many operations can be performed between two ``Array`` objects.
For these to be valid the dtypes of the ``Array`` objects must be numerical, that is they must represent an integer or floating point value.
Some operations have tighter restrictions, such as the shift operators ``<<`` and ``>>`` requiring integers only.
The dtype of the resulting ``Array`` is calculated by applying these rules:
0. For comparison operators (``<``, ``>=``, ``==``, ``!=`` etc.) the result is always an ``Array`` of dtype ``'bool'``.
For other operators, one of the two input ``Array`` dtypes is used as the ouput dtype by applying the remaining rules in order until a winner is found:
1. Floating point types always win against integer types.
2. Signed integer types always win against unsigned integer types.
3. Longer types win against shorter types.
4. In a tie the first type wins.
Some examples should help illustrate:
======= ================ ============ ================ === ==================
Rule 0 ``'uint8'`` ``<=`` ``'float64'`` → ``'bool'``
Rule 1 ``'int32'`` ``+`` ``'float16'`` → ``'float16'``
Rule 2 ``'uint20'`` ``//`` ``'int10'`` → ``'int10'``
Rule 3 ``'int8'`` ``*`` ``'int16'`` → ``'int16'``
Rule 4 ``'float16'`` ``-=`` ``'bfloat'`` → ``'float16'``
======= ================ ============ ================ === ==================
Comparison operators
""""""""""""""""""""
Comparison operators can operate between two ``Array`` objects, or between an ``Array`` and a scalar quantity (usually a number).
Note that they always produce an ``Array`` of :attr:`~Array.dtype` ``'bool'``, including the equality and inequality operators.
To test the boolean equality of two Arrays use the :meth:`~Array.equals` method instead.
.. method:: Array.__eq__(self, other: int | float | str | BitsType | Array) -> Array
``a1 == a2``
.. method:: Array.__ne__(self, other: int | float | str | BitsType | Array) -> Array
``a1 != a2``
.. method:: Array.__lt__(self, other: int | float | Array) -> Array
``a1 < a2``
.. method:: Array.__le__(self, other: int | float | Array) -> Array
``a1 <= a2``
.. method:: Array.__gt__(self, other: int | float | Array) -> Array
``a1 > a2``
.. method:: Array.__ge__(self, other: int | float | Array) -> Array
``a1 >= a2``
Numerical operators
"""""""""""""""""""
.. method:: Array.__add__(other: int | float | Array) -> Array
``a + x``
.. method:: Array.__sub__(self, other: int | float | Array) -> Array
``a - x``
.. method:: Array.__mul__(self, other: int | float | Array) -> Array
``a * x``
.. method:: Array.__truediv__(self, other: int | float | Array) -> Array
``a / x``
.. method:: Array.__floordiv__(self, other: int | float | Array) -> Array
``a // x``
.. method:: Array.__rshift__(self, other: int | Array) -> Array
``a >> i``
.. method:: Array.__lshift__(self, other: int | Array) -> Array
``a << i``
.. method:: Array.__mod__(self, other: int | Array) -> Array
``a % i``
.. method:: Array.__neg__(self) -> Array
``-a``
.. method:: Array.__abs__(self) -> Array
``abs(a)``
Bitwise operators
"""""""""""""""""
.. method:: Array.__and__(self, other: Bits) -> Array
``a & bs``
>>> a &= '0b1110'
.. method:: Array.__or__(self, other: Bits) -> Array
``a | bs``
>>> a |= '0x7fff'
.. method:: Array.__xor__(self, other: Bits) -> Array
``a ^ bs``
>>> a ^= bytearray([56, 23])
Python language operators
"""""""""""""""""""""""""
.. method:: Array.__len__(self) -> int
``len(a)``
Return the number of elements in the Array. ::
>>> a = Array('uint20', [1, 2, 3])
>>> len(a)
3
>>> a.dtype = 'uint1'
>>> len(a)
60
.. method:: Array.__getitem__(self, key: int | slice) -> float | int | str | bytes | Array
``a[i]``
``a[start:end:step]``
.. method:: Array.__setitem__(self, key: int | slice, value) -> None
``a[i] = x``
``a[start:end:step] = x``
.. method:: Array.__delitem__(self, key: int | slice) -> None
``del a[i]``
``del[start:end:step]``
Properties
----------
.. attribute:: Array.data
:type: BitArray
The bit data of the ``Array``, as a ``BitArray``. Read and write, and can be freely manipulated with all of ``BitArray`` methods.
Note that some ``Array`` methods such as :meth:`~Array.append` and :meth:`~Array.extend` require the :attr:`~Array.data` to have a length that is a multiple of the ``Array``'s :attr:`~Array.itemsize`.
.. attribute:: Array.dtype
:type: str
The data type string used to initialise the ``Array`` type. Read and write.
Changing the ``dtype`` for an already formed ``Array`` will cause all of the bit data to be reinterpreted and can change the length of the ``Array``.
However, changing the ``dtype`` won't change the underlying bit data in any way.
Note that some ``Array`` methods such as :meth:`~Array.append` and :meth:`~Array.extend` require the bit data to have a length that is a multiple of the ``Array``'s :attr:`~Array.itemsize`.
.. attribute:: Array.itemsize
:type: int
The size *in bits* of each item in the ``Array``. Read-only.
Note that this gives a value in bits, unlike the equivalent in the ``array`` module which gives a value in bytes. ::
>>> a = Array('>h')
>>> b = Array('bool')
>>> a.itemsize
16
>>> b.itemsize
1
.. attribute:: Array.trailing_bits
:type: BitArray
A ``BitArray`` object equal to the end of the ``data`` that is not a multiple of the ``itemsize``. Read only.
This will typically be an empty ``BitArray``, but if the ``dtype`` or the ``data`` of an ``Array`` object has been altered after its creation then there may be left-over bits at the end of the data.
Note that any methods that append items to the ``Array`` will fail with a ``ValueError`` if there are any trailing bits.
bitstring-bitstring-4.1.4/doc/bitarray.rst 0000664 0000000 0000000 00000035347 14531676336 0020676 0 ustar 00root root 0000000 0000000 .. currentmodule:: bitstring
BitArray
========
.. class:: BitArray(__auto: BitsType | int | None, length: int | None = None, offset: int | None = None, **kwargs)
The :class:`Bits` class is the base class for :class:`BitArray` and so (with the exception of :meth:`~Bits.__hash__`) all of its methods are also available for :class:`BitArray` objects. The initialiser is also the same as for :class:`Bits` and so won't be repeated here.
A :class:`BitArray` is a mutable :class:`Bits`, and so the one thing all of the methods listed here have in common is that they can modify the contents of the bitstring.
Methods
-------
.. method:: BitArray.append(bs: BitsType) -> None
Join a :class:`BitArray` to the end of the current :class:`BitArray`. ::
>>> s = BitArray('0xbad')
>>> s.append('0xf00d')
>>> s
BitArray('0xbadf00d')
.. method:: BitArray.byteswap(fmt: str | int | Iterable[int] | None = None, start: int | None = None, end: int | None = None, repeat: bool = True) -> int
Change the endianness of the :class:`BitArray` in-place according to *fmt*. Return the number of swaps done.
The *fmt* can be an integer, an iterable of integers or a compact format string similar to those used in :func:`pack` (described in :ref:`compact_format`). It defaults to 0, which means reverse as many bytes as possible. The *fmt* gives a pattern of byte sizes to use to swap the endianness of the :class:`BitArray`. Note that if you use a compact format string then the endianness identifier (``<``, ``>`` or ``=``) is not needed, and if present it will be ignored.
*start* and *end* optionally give a slice to apply the transformation to (it defaults to the whole :class:`BitArray`). If *repeat* is ``True`` then the byte swapping pattern given by the *fmt* is repeated in its entirety as many times as possible.
>>> s = BitArray('0x00112233445566')
>>> s.byteswap(2)
3
>>> s
BitArray('0x11003322554466')
>>> s.byteswap('h')
3
>>> s
BitArray('0x00112233445566')
>>> s.byteswap([2, 5])
1
>>> s
BitArray('0x11006655443322')
It can also be used to swap the endianness of the whole :class:`BitArray`. ::
>>> s = BitArray('uintle32=1234')
>>> s.byteswap()
>>> print(s.uintbe)
1234
.. method:: BitArray.clear() -> None
Removes all bits from the bitstring.
``s.clear()`` is equivalent to ``del s[:]`` and simply makes the bitstring empty.
.. method:: BitArray.insert(bs: BitsType, pos: int) -> None
Inserts *bs* at *pos*.
When used with the :class:`BitStream` class the *pos* is optional, and if not present the current bit position will be used. After insertion the property :attr:`~ConstBitStream.pos` will be immediately after the inserted bitstring. ::
>>> s = BitStream('0xccee')
>>> s.insert('0xd', 8)
>>> s
BitStream('0xccdee')
>>> s.insert('0x00')
>>> s
BitStream('0xccd00ee')
.. method:: BitArray.invert(pos: int | Iterable[int] | None = None) -> None
Inverts one or many bits from ``1`` to ``0`` or vice versa.
*pos* can be either a single bit position or an iterable of bit positions. Negative numbers are treated in the same way as slice indices and it will raise :exc:`IndexError` if ``pos < -len(s)`` or ``pos > len(s)``. The default is to invert the entire :class:`BitArray`. ::
>>> s = BitArray('0b111001')
>>> s.invert(0)
>>> s.bin
'011001'
>>> s.invert([-2, -1])
>>> s.bin
'011010'
>>> s.invert()
>>> s.bin
'100101'
.. method:: BitArray.overwrite(bs: BitsType, pos: int) -> None
Replaces the contents of the current :class:`BitArray` with *bs* at *pos*.
When used with the :class:`BitStream` class the *pos* is optional, and if not present the current bit position will be used. After insertion the property :attr:`~ConstBitStream.pos` will be immediately after the overwritten bitstring. ::
>>> s = BitArray(length=10)
>>> s.overwrite('0b111', 3)
>>> s
BitArray('0b0001110000')
>>> s.pos
6
.. method:: BitArray.prepend(bs: BitsType) -> None
Inserts *bs* at the beginning of the current :class:`BitArray`. ::
>>> s = BitArray('0b0')
>>> s.prepend('0xf')
>>> s
BitArray('0b11110')
.. method:: BitArray.replace(old: BitsType, new: BitsType, start: int | None = None, end: int | None = None, count: int | None = None, bytealigned: bool | None = None) -> int
Finds occurrences of *old* and replaces them with *new*. Returns the number of replacements made.
If *bytealigned* is ``True`` then replacements will only be made on byte boundaries. *start* and *end* give the search range and default to ``0`` and :attr:`~Bits.len` respectively. If *count* is specified then no more than this many replacements will be made. ::
>>> s = BitArray('0b0011001')
>>> s.replace('0b1', '0xf')
3
>>> print(s.bin)
0011111111001111
>>> s.replace('0b1', '', count=6)
6
>>> print(s.bin)
0011001111
.. method:: BitArray.reverse(start: int | None = None, end: int | None = None) -> None
Reverses bits in the :class:`BitArray` in-place.
*start* and *end* give the range of bits to reverse and default to ``0`` and :attr:`~Bits.len` respectively. ::
>>> a = BitArray('0b000001101')
>>> a.reverse()
>>> a.bin
'101100000'
>>> a.reverse(0, 4)
>>> a.bin
'110100000'
.. method:: BitArray.rol(bits: int, start: int | None = None, end: int | None = None) -> None
Rotates the contents of the :class:`BitArray` in-place by *bits* bits to the left.
*start* and *end* define the slice to use and default to ``0`` and :attr:`~Bits.len` respectively.
Raises :exc:`ValueError` if ``bits < 0``. ::
>>> s = BitArray('0b01000001')
>>> s.rol(2)
>>> s.bin
'00000101'
.. method:: BitArray.ror(bits: int, start: int | None = None, end: int | None = None) -> None
Rotates the contents of the :class:`BitArray` in-place by *bits* bits to the right.
*start* and *end* define the slice to use and default to ``0`` and :attr:`~Bits.len` respectively.
Raises :exc:`ValueError` if ``bits < 0``.
.. method:: BitArray.set(value: bool, pos: int | Iterable[int] | None = None) -> None
Sets one or many bits to either ``1`` (if *value* is ``True``) or ``0`` (if *value* isn't ``True``). *pos* can be either a single bit position or an iterable of bit positions. Negative numbers are treated in the same way as slice indices and it will raise :exc:`IndexError` if ``pos < -len(s)`` or ``pos > len(s)``. The default is to set every bit in the :class:`BitArray`.
Using ``s.set(True, x)`` can be more efficient than other equivalent methods such as ``s[x] = 1``, ``s[x] = "0b1"`` or ``s.overwrite('0b1', x)``, especially if many bits are being set. In particular using a ``range`` object as an iterable is treated as a special case and is done efficiently. ::
>>> s = BitArray('0x0000')
>>> s.set(True, -1)
>>> print(s)
0x0001
>>> s.set(1, (0, 4, 5, 7, 9))
>>> s.bin
'1000110101000001'
>>> s.set(0)
>>> s.bin
'0000000000000000'
>>> s.set(1, range(0, len(s), 2))
>>> s.bin
'1010101010101010'
Properties
----------
Note that the ``bin``, ``oct``, ``hex``, ``int``, ``uint`` and ``float`` properties can all be shortened to their initial letter.
Properties can also have a length in bits appended to them to make properties such as ``u8`` or ``floatle64`` (with the exception of the ``bytes`` property which uses a unit of bytes instead of bits, so ``bytes4`` is 32 bits long). These properties with lengths can be used to quickly create a new bitstring. ::
>>> a = BitArray()
>>> a.f32 = 17.6
>>> a.h
'418ccccd'
>>> a.i7 = -1
>>> a.b
'1111111'
.. attribute:: BitArray.bin
:type: str
:noindex:
.. attribute:: BitArray.b
:type: str
:noindex:
Writable version of :attr:`Bits.bin`.
.. attribute:: BitArray.bfloat
:type: float
:noindex:
.. attribute:: BitArray.bfloatbe
:type: float
:noindex:
.. attribute:: BitArray.bfloatle
:type: float
:noindex:
.. attribute:: BitArray.bfloatne
:type: float
:noindex:
Writable versions of :attr:`Bits.bfloat` / :attr:`Bits.bfloatbe` / :attr:`Bits.bfloatle` / :attr:`Bits.bfloatne`.
.. attribute:: BitArray.bool
:type: bool
:noindex:
Writable version of :attr:`Bits.bool`.
.. attribute:: BitArray.bytes
:type: bytes
:noindex:
Writable version of :attr:`Bits.bytes`.
.. attribute:: BitArray.hex
:type: str
:noindex:
.. attribute:: BitArray.h
:type: str
:noindex:
Writable version of :attr:`Bits.hex`.
.. attribute:: BitArray.int
:type: int
:noindex:
.. attribute:: BitArray.i
:type: int
:noindex:
Writable version of :attr:`Bits.int`. The properties can have a bit length appended to it such as ``i32`` or ``int5`` to specify the new length of the bitstring. Using a length too small to contain the value given will raise a :exc:`CreationError`.
When used as a setter without a new length the value must fit into the current length of the :class:`BitArray`, else a :exc:`ValueError` will be raised. ::
>>> s = BitArray('0xf3')
>>> s.int
-13
>>> s.int = 1232
ValueError: int 1232 is too large for a BitArray of length 8.
.. attribute:: BitArray.intbe
:type: int
:noindex:
Writable version of :attr:`Bits.intbe`.
When used as a setter the value must fit into the current length of the :class:`BitArray`, else a :exc:`ValueError` will be raised.
.. attribute:: BitArray.intle
:type: int
:noindex:
Writable version of :attr:`Bits.intle`.
When used as a setter the value must fit into the current length of the :class:`BitArray`, else a :exc:`ValueError` will be raised.
.. attribute:: BitArray.intne
:type: int
:noindex:
Writable version of :attr:`Bits.intne`.
When used as a setter the value must fit into the current length of the :class:`BitArray`, else a :exc:`ValueError` will be raised.
.. attribute:: BitArray.float
:type: float
:noindex:
.. attribute:: BitArray.floatbe
:type: float
:noindex:
.. attribute:: BitArray.f
:type: float
:noindex:
Writable version of :attr:`Bits.float`. The standard ``float``, the big-endian ``floatbe`` and the shortened ``f`` are all equivalent.
The properties can have a bit length appended to them such as ``f16`` or ``floatle64`` to specify the new length of the bitstring. Using a length that doesn't support any floating point types will raise a :exc:`CreationError`.
.. attribute:: BitArray.floatle
:type: float
:noindex:
Writable version of :attr:`Bits.floatle`.
.. attribute:: BitArray.floatne
:type: float
:noindex:
Writable version of :attr:`Bits.floatne`.
.. attribute:: BitArray.e4m3float
:type: float
:noindex:
Writable version of :attr:`Bits.e4m3float`.
.. attribute:: BitArray.e5m2float
:type: float
:noindex:
Writable version of :attr:`Bits.e5m2float`.
.. attribute:: BitArray.oct
:type: str
:noindex:
.. attribute:: BitArray.o
:type: str
:noindex:
Writable version of :attr:`Bits.oct`.
.. attribute:: BitArray.se
:type: int
:noindex:
Writable version of :attr:`Bits.se`.
.. attribute:: BitArray.ue
:type: int
:noindex:
Writable version of :attr:`Bits.uie`.
.. attribute:: BitArray.sie
:type: int
:noindex:
Writable version of :attr:`Bits.sie`.
.. attribute:: BitArray.uie
:type: int
:noindex:
Writable version of :attr:`Bits.ue`.
.. attribute:: BitArray.uint
:type: int
:noindex:
.. attribute:: BitArray.u
:type: int
:noindex:
Writable version of :attr:`Bits.uint`.
When used as a setter the value must fit into the current length of the :class:`BitArray`, else a :exc:`ValueError` will be raised.
.. attribute:: BitArray.uintbe
:type: int
:noindex:
Writable version of :attr:`Bits.uintbe`.
When used as a setter the value must fit into the current length of the :class:`BitArray`, else a :exc:`ValueError` will be raised.
.. attribute:: BitArray.uintle
:type: int
:noindex:
Writable version of :attr:`Bits.uintle`.
When used as a setter the value must fit into the current length of the :class:`BitArray`, else a :exc:`ValueError` will be raised.
.. attribute:: BitArray.uintne
:type: int
:noindex:
Writable version of :attr:`Bits.uintne`.
When used as a setter the value must fit into the current length of the :class:`BitArray`, else a :exc:`ValueError` will be raised.
Special Methods
---------------
.. method:: BitArray.__delitem__(key)
``del s[start:end:step]``
Deletes the slice specified.
.. method:: BitArray.__iadd__(bs)
``s1 += s2``
Appends *bs* to the current bitstring.
Note that for :class:`BitArray` objects this will be an in-place change, whereas for :class:`Bits` objects using ``+=`` will not call this method - instead a new object will be created (it is equivalent to a copy and an :meth:`~Bits.__add__`). ::
>>> s = BitArray(ue=423)
>>> s += BitArray(ue=12)
>>> s.read('ue')
423
>>> s.read('ue')
12
.. method:: BitArray.__iand__(bs)
``s &= bs``
In-place bit-wise AND between two bitstrings. If the two bitstrings are not the same length then a :exc:`ValueError` is raised.
.. method:: BitArray.__ilshift__(n)
``s <<= n``
Shifts the bits in-place *n* bits to the left. The *n* right-most bits will become zeros and bits shifted off the left will be lost.
.. method:: BitArray.__imul__(n)
``s *= n``
In-place concatenation of *n* copies of the current bitstring.
>>> s = BitArray('0xbad')
>>> s *= 3
>>> s.hex
'badbadbad'
.. method:: BitArray.__ior__(bs)
``s |= bs``
In-place bit-wise OR between two bitstrings. If the two bitstrings are not the same length then a :exc:`ValueError` is raised.
.. method:: BitArray.__irshift__(n)
``s >>= n``
Shifts the bits in-place *n* bits to the right. The *n* left-most bits will become zeros and bits shifted off the right will be lost.
.. method:: BitArray.__ixor__(bs)
``s ^= bs``
In-place bit-wise XOR between two bitstrings. If the two bitstrings are not the same length then a :exc:`ValueError` is raised.
.. method:: BitArray.__setitem__(key, value)
``s1[start:end:step] = s2``
Replaces the slice specified with a new value. ::
>>> s = BitArray('0x00000000')
>>> s[::8] = '0xf'
>>> print(s)
0x80808080
>>> s[-12:] = '0xf'
>>> print(s)
0x80808f
bitstring-bitstring-4.1.4/doc/bits.rst 0000664 0000000 0000000 00000067746 14531676336 0020032 0 ustar 00root root 0000000 0000000 .. currentmodule:: bitstring
Bits
====
The ``Bits`` class is the simplest type in the bitstring module, and represents an immutable sequence of bits. This is the best class to use if you will not need to modify the data after creation and don't need streaming methods.
.. class:: Bits(__auto: BitsType | int | None, length: int | None = None, offset: int | None = None, **kwargs)
Creates a new bitstring. You must specify either no initialiser, just an 'auto' value as the first parameter, or one of the keyword arguments ``bytes``, ``bin``, ``hex``, ``oct``, ``uint``, ``int``, ``uintbe``, ``intbe``, ``uintle``, ``intle``, ``uintne``, ``intne``, ``se``, ``ue``, ``sie``, ``uie``, ``float``, ``floatbe``, ``floatle``, ``floatne``, ``e4m3float``, ``e5m2float``, ``bfloat``, ``bfloatbe``, ``bfloatle``, ``bfloatne``, ``bool`` or ``filename``. If no initialiser is given then a zeroed bitstring of ``length`` bits is created.
The initialiser for the :class:`Bits` class is precisely the same as for :class:`BitArray`, :class:`BitStream` and :class:`ConstBitStream`.
``offset`` is available when using the ``bytes`` or ``filename`` initialisers. It gives a number of bits to ignore at the start of the bitstring.
Specifying ``length`` is mandatory when using the various integer initialisers. It must be large enough that a bitstring can contain the integer in ``length`` bits. It must also be specified for the float initialisers (the only valid values are 16, 32 and 64). It is optional for the ``bytes`` and ``filename`` initialisers and can be used to truncate data from the end of the input value. ::
>>> s1 = Bits(hex='0x934')
>>> s2 = Bits(oct='0o4464')
>>> s3 = Bits(bin='0b001000110100')
>>> s4 = Bits(int=-1740, length=12)
>>> s5 = Bits(uint=2356, length=12)
>>> s6 = Bits(bytes=b'\x93@', length=12)
>>> s1 == s2 == s3 == s4 == s5 == s6
True
See also :ref:`auto_init`, which allows many different types to be used to initialise a bitstring. ::
>>> s = Bits('uint12=32, 0b110')
>>> t = Bits('0o755, ue=12, int:3=-1')
In the methods below we use ``BitsType`` to indicate that any of the types that can auto initialise can be used.
Methods
-------
.. method:: Bits.all(value: bool, pos: Iterable[int] | None = None) -> bool
Returns ``True`` if all of the specified bits are all set to *value*, otherwise returns ``False``.
If *value* is ``True`` then ``1`` bits are checked for, otherwise ``0`` bits are checked for.
*pos* should be an iterable of bit positions. Negative numbers are treated in the same way as slice indices and it will raise an :exc:`IndexError` if ``pos < -len(s)`` or ``pos > len(s)``. It defaults to the whole bitstring.
>>> s = Bits('int15=-1')
>>> s.all(True, [3, 4, 12, 13])
True
>>> s.all(1)
True
.. method:: Bits.any(value: bool, pos: Iterable[int] | None = None) -> bool
Returns ``True`` if any of the specified bits are set to *value*, otherwise returns ``False``.
If *value* is ``True`` then ``1`` bits are checked for, otherwise ``0`` bits are checked for.
*pos* should be an iterable of bit positions. Negative numbers are treated in the same way as slice indices and it will raise an :exc:`IndexError` if ``pos < -len(s)`` or ``pos > len(s)``. It defaults to the whole bitstring.
>>> s = Bits('0b11011100')
>>> s.any(False, range(6))
True
>>> s.any(1)
True
.. method:: Bits.copy() -> Bits
Returns a copy of the bitstring.
``s.copy()`` is equivalent to the shallow copy ``s[:]`` and creates a new copy of the bitstring in memory.
.. method:: Bits.count(value: bool) -> int
Returns the number of bits set to *value*.
*value* can be ``True`` or ``False`` or anything that can be cast to a bool, so you could equally use ``1`` or ``0``.
>>> s = BitArray(1000000)
>>> s.set(1, [4, 44, 444444])
>>> s.count(1)
3
>>> s.count(False)
999997
If you need to count more than just single bits you can use :meth:`~Bits.findall`, for example ``len(list(s.findall('0xabc')))``.
Note that if the bitstring is very sparse, as in the example here, it could be quicker to find and count all the set bits with something like ``len(list(s.findall('0b1')))``. For bitstrings with more entropy the ``count`` method will be much quicker than finding.
.. method:: Bits.cut(bits: int, start: int | None = None, end: int | None = None, count: int | None = None) -> Iterator[Bits]
Returns a generator for slices of the bitstring of length *bits*.
At most *count* items are returned and the range is given by the slice *[start:end]*, which defaults to the whole bitstring. ::
>>> s = BitArray('0x1234')
>>> for nibble in s.cut(4):
... s.prepend(nibble)
>>> print(s)
0x43211234
.. method:: Bits.endswith(bs: BitsType, start: int | None = None, end: int | None = None) -> bool
Returns ``True`` if the bitstring ends with the sub-string *bs*, otherwise returns ``False``.
A slice can be given using the *start* and *end* bit positions and defaults to the whole bitstring. ::
>>> s = Bits('0x35e22')
>>> s.endswith('0b10, 0x22')
True
>>> s.endswith('0x22', start=13)
False
.. method:: Bits.find(bs: BitsType, start: int | None = None, end: int | None = None, bytealigned: bool | None = None) -> Tuple[int] | Tuple[()]
Searches for *bs* in the current bitstring and sets :attr:`~ConstBitStream.pos` to the start of *bs* and returns it in a tuple if found, otherwise it returns an empty tuple.
The reason for returning the bit position in a tuple is so that it evaluates as True even if the bit position is zero. This allows constructs such as ``if s.find('0xb3'):`` to work as expected.
If *bytealigned* is ``True`` then it will look for *bs* only at byte aligned positions (which is generally much faster than searching for it in every possible bit position). *start* and *end* give the search range and default to the whole bitstring. ::
>>> s = Bits('0x0023122')
>>> s.find('0b000100', bytealigned=True)
(16,)
.. method:: Bits.findall(bs: BitsType, start: int | None = None, end: int | None = None, count: int | None = None, bytealigned: bool | None = None) -> Iterable[int]
Searches for all occurrences of *bs* (even overlapping ones) and returns a generator of their bit positions.
If *bytealigned* is ``True`` then *bs* will only be looked for at byte aligned positions. *start* and *end* optionally define a search range and default to the whole bitstring.
The *count* parameter limits the number of items that will be found - the default is to find all occurrences. ::
>>> s = Bits('0xab220101')*5
>>> list(s.findall('0x22', bytealigned=True))
[8, 40, 72, 104, 136]
.. method:: Bits.join(sequence: Iterable) -> Bits
Returns the concatenation of the bitstrings in the iterable *sequence* joined with ``self`` as a separator. ::
>>> s = Bits().join(['0x0001ee', 'uint:24=13', '0b0111'])
>>> print(s)
0x0001ee00000d7
>>> s = Bits('0b1').join(['0b0']*5)
>>> print(s.bin)
010101010
.. method:: Bits.pp(fmt: str | None = None, width: int = 120, sep: str = ' ', show_offset: bool = True, stream: TextIO = sys.stdout) -> None
Pretty print the bitstring's value according to the *fmt*. Either a single, or two comma separated formats can be specified, together with options for setting the maximum display *width*, the number of bits to display in each group, and the separator to print between groups.
>>> s = Bits(int=-98987987293452, length=200)
>>> s.pp(width=80)
0: 11111111 11111111 11111111 11111111 11111111 11111111 ff ff ff ff ff ff
48: 11111111 11111111 11111111 11111111 11111111 11111111 ff ff ff ff ff ff
96: 11111111 11111111 11111111 11111111 11111111 11111111 ff ff ff ff ff ff
144: 11111111 10100101 11111000 10010000 00101110 00101010 ff a5 f8 90 2e 2a
192: 11110100 f4
>>> s.pp('h16, b', width=80, show_offset=False, sep=' / ')
ffff / ffff / ffff 1111111111111111 / 1111111111111111 / 1111111111111111
ffff / ffff / ffff 1111111111111111 / 1111111111111111 / 1111111111111111
ffff / ffff / ffff 1111111111111111 / 1111111111111111 / 1111111111111111
ffa5 / f890 / 2e2a 1111111110100101 / 1111100010010000 / 0010111000101010
f4 11110100
The available formats are ``'bin'``, ``'oct'``, ``'hex'`` and ``'bytes'``. A bit length can be specified after the format (with an optional `:`) to give the number of bits represented by each group, otherwise the default is based on the format or formats selected. Using a length of zero removes all separators and displays one block of characters per line for each format in *fmt* (e.g. ``'hex0'``).
The ``'hex'``, ``'oct'`` and ``'bin'`` format string can be replaced with just their initial letter.
For the ``'bytes'`` format, characters from the 'Latin Extended-A' unicode block are used for non-ASCII and unprintable characters.
If the bitstring cannot be represented in a format due to it's length not being a multiple of the number of bits represented by each character then an :exc:`InterpretError` will be raised.
An output *stream* can be specified. This should be an object with a ``write`` method and the default is ``sys.stdout``.
.. method:: Bits.rfind(bs: BitsType, start: int | None = None, end: int | None = None, bytealigned: bool | None = None) -> Tuple[int] | Tuple[()]
Searches backwards for *bs* in the current bitstring and sets :attr:`~ConstBitStream.pos` to the start of *bs* and returns it in a tuple if found, otherwise it returns an empty tuple.
The reason for returning the bit position in a tuple is so that it evaluates as True even if the bit position is zero. This allows constructs such as ``if s.rfind('0xb3'):`` to work as expected.
If *bytealigned* is ``True`` then it will look for *bs* only at byte aligned positions. *start* and *end* give the search range and default to ``0`` and :attr:`len` respectively.
Note that as it's a reverse search it will start at *end* and finish at *start*. ::
>>> s = Bits('0o031544')
>>> s.rfind('0b100')
(15,)
>>> s.rfind('0b100', end=17)
(12,)
.. method:: Bits.split(delimiter: BitsType, start: int | None = None, end: int | None = None, count: int | None = None, bytealigned: bool | None = None) -> Iterable[Bits]
Splits the bitstring into sections that start with *delimiter*. Returns a generator for bitstring objects.
The first item generated is always the bits before the first occurrence of delimiter (even if empty). A slice can be optionally specified with *start* and *end*, while *count* specifies the maximum number of items generated.
If *bytealigned* is ``True`` then the delimiter will only be found if it starts at a byte aligned position. ::
>>> s = Bits('0x42423')
>>> [bs.bin for bs in s.split('0x4')]
['', '01000', '01001000', '0100011']
.. method:: Bits.startswith(bs: BitsType, start: int | None = None, end: int | None = None) -> bool
Returns ``True`` if the bitstring starts with the sub-string *bs*, otherwise returns ``False``.
A slice can be given using the *start* and *end* bit positions and defaults to the whole bitstring. ::
>>> s = BitArray('0xef133')
>>> s.startswith('0b111011')
True
.. method:: Bits.tobitarray() -> bitarray.bitarray
Returns the bitstring as a ``bitarray`` object.
Converts the bitstring to an equivalent ``bitarray`` object from the ``bitarray`` package.
This shouldn't be confused with the ``BitArray`` type provided in the ``bitstring`` package - the ``bitarray`` package is a separate third-party way of representing binary objects.
Note that ``BitStream`` and ``ConstBitStream`` types that have a bit position do support this method but the bit position information will be lost.
.. method:: Bits.tobytes() -> bytes
Returns the bitstring as a ``bytes`` object.
The returned value will be padded at the end with between zero and seven ``0`` bits to make it byte aligned.
This differs from using the plain :attr:`~Bits.bytes` property which will not pad with zero bits and instead raises an exception if the bitstring is not a whole number of bytes long.
This method can also be used to output your bitstring to a file - just open a file in binary write mode and write the function's output. ::
>>> s = Bits(bytes=b'hello')
>>> s += '0b01'
>>> s.tobytes()
b'hello@'
This is equivalent to casting to a bytes object directly: ::
>>> bytes(s)
b'hello@'
.. method:: Bits.tofile(f: BinaryIO) -> None
Writes the bitstring to the file object *f*, which should have been opened in binary write mode.
The data written will be padded at the end with between zero and seven ``0`` bits to make it byte aligned. ::
>>> f = open('newfile', 'wb')
>>> Bits('0x1234').tofile(f)
.. method:: Bits.unpack(fmt: str | list[str | int], **kwargs) -> list[float | int | str | None | Bits]
Interprets the whole bitstring according to the *fmt* string or iterable and returns a list of bitstring objects.
A dictionary or keyword arguments can also be provided. These will replace length identifiers in the format string.
*fmt* is an iterable or a string with comma separated tokens that describe how to interpret the next bits in the bitstring. See the :ref:`format_tokens` for details. ::
>>> s = Bits('int4=-1, 0b1110')
>>> i, b = s.unpack('int:4, bin')
If a token doesn't supply a length (as with ``bin`` above) then it will try to consume the rest of the bitstring. Only one such token is allowed.
The ``unpack`` method is a natural complement of the :func:`pack` function. ::
s = bitstring.pack('uint10, hex, int13, 0b11', 130, '3d', -23)
a, b, c, d = s.unpack('uint10, hex, int13, bin2')
Properties
----------
Note that the ``bin``, ``oct``, ``hex``, ``int``, ``uint`` and ``float`` properties can all be shortened to their initial letter. Properties can also have a length in bits appended to them to such as ``u8`` or ``f64`` (for the ``bytes`` property the length is interpreted in bytes instead of bits). These properties with lengths will cause an :exc:`InterpretError` to be raised if the bitstring is not of the specified length.
.. attribute:: Bits.bin
:type: str
.. attribute:: Bits.b
:type: str
:noindex:
Property for the representation of the bitstring as a binary string.
.. attribute:: Bits.bfloat
:type: float
.. attribute:: Bits.bfloatbe
:type: float
Property for the 2 byte bfloat floating point representation of the bitstring.
The bitstring must be 16 bits long to support this floating point interpretation, otherwise an :exc:`InterpretError` will be raised.
The :attr:`bfloat` property is bit-wise big-endian, which as all floats must be whole-byte is exactly equivalent to the byte-wise big-endian :attr:`bfloatbe`.
The ``bfloat`` properties are specialised representations mainly used in machine learning. They are essentially the first half of the IEEE 32-bit floats, so have the same range but with less accuracy. If you don't know what a bfloat is then you almost certainly want to use the ``float`` properties instead. See :ref:`Exotic floats` for more information.
.. attribute:: Bits.bfloatle
:type: float
Property for the byte-wise little-endian 2 byte bfloat floating point representation of the bitstring.
.. attribute:: Bits.bfloatne
:type: float
Property for the byte-wise native-endian 2 byte bfloat floating point representation of the bitstring.
.. attribute:: Bits.bool
:type: bool
Property for representing the bitstring as a boolean (``True`` or ``False``).
If the bitstring is not a single bit then the getter will raise an :exc:`InterpretError`.
.. attribute:: Bits.bytes
:type: bytes
Property representing the underlying byte data that contains the bitstring.
When used as a getter the bitstring must be a whole number of byte long or a :exc:`InterpretError` will be raised.
An alternative is to use the :meth:`tobytes` method, which will pad with between zero and seven ``0`` bits to make it byte aligned if needed. ::
>>> s = Bits('0x12345678')
>>> s.bytes
b'\x124Vx'
.. attribute:: Bits.hex
:type: str
.. attribute:: Bits.h
:type: str
:noindex:
Property representing the hexadecimal value of the bitstring.
If the bitstring is not a multiple of four bits long then getting its hex value will raise an :exc:`InterpretError`. ::
>>> s = Bits(bin='1111 0000')
>>> s.hex
'f0'
.. attribute:: Bits.int
:type: int
.. attribute:: Bits.i
:type: int
:noindex:
Property for the signed two’s complement integer representation of the bitstring.
.. attribute:: Bits.intbe
:type: int
Property for the byte-wise big-endian signed two's complement integer representation of the bitstring.
Only valid for whole-byte bitstrings, in which case it is equal to ``s.int``, otherwise an :exc:`InterpretError` is raised.
.. attribute:: Bits.intle
:type: int
Property for the byte-wise little-endian signed two's complement integer representation of the bitstring.
Only valid for whole-byte bitstring, in which case it is equal to ``s[::-8].int``, i.e. the integer representation of the byte-reversed bitstring.
.. attribute:: Bits.intne
:type: int
Property for the byte-wise native-endian signed two's complement integer representation of the bitstring.
Only valid for whole-byte bitstrings, and will equal either the big-endian or the little-endian integer representation depending on the platform being used.
.. attribute:: Bits.float
:type: float
.. attribute:: Bits.floatbe
:type: float
.. attribute:: Bits.f
:type: float
:noindex:
Property for the floating point representation of the bitstring.
The bitstring must be 16, 32 or 64 bits long to support the floating point interpretations, otherwise an :exc:`InterpretError` will be raised.
If the underlying floating point methods on your machine are not IEEE 754 compliant then using the float interpretations is undefined (this is unlikely unless you're on some very unusual hardware).
The :attr:`float` property is bit-wise big-endian, which as all floats must be whole-byte is exactly equivalent to the byte-wise big-endian :attr:`floatbe`.
.. attribute:: Bits.floatle
:type: float
Property for the byte-wise little-endian floating point representation of the bitstring.
.. attribute:: Bits.floatne
:type: float
Property for the byte-wise native-endian floating point representation of the bitstring.
.. attribute:: Bits.e4m3float
:type: float
Property for an 8 bit floating point representation with 4 exponent bits and 3 mantissa bits.
See :ref:`Exotic floats` for more information.
.. attribute:: Bits.e5m2float
:type: float
Property for an 8 bit floating point representation with 5 exponent bits and 2 mantissa bits.
See :ref:`Exotic floats` for more information.
.. attribute:: Bits.len
:type: int
.. attribute:: Bits.length
:type: int
:noindex:
Read-only property that give the length of the bitstring in bits (:attr:`len` and ``length`` are equivalent).
Using the ``len()`` built-in function is preferred in almost all cases, but these properties are available for backward compatibility. The only occasion where the properties are needed is if a 32-bit build of Python is being used and you have a bitstring whose length doesn't fit in a 32-bit unsigned integer. In that case ``len(s)`` may fail with an :exc:`OverflowError`, whereas ``s.len`` will still work. With 64-bit Python the problem shouldn't occur unless you have more than a couple of exabytes of data!
.. attribute:: Bits.oct
:type: str
.. attribute:: Bits.o
:type: str
:noindex:
Property for the octal representation of the bitstring.
If the bitstring is not a multiple of three bits long then getting its octal value will raise a :exc:`InterpretError`. ::
>>> s = Bits('0b111101101')
>>> s.oct
'755'
>>> s.oct = '01234567'
>>> s.oct
'01234567'
.. attribute:: Bits.se
:type: int
Property for the signed exponential-Golomb code representation of the bitstring.
When used as a getter an :exc:`InterpretError` will be raised if the bitstring is not a single code. ::
>>> s = BitArray(se=-40)
>>> s.bin
0000001010001
>>> s += '0b1'
>>> s.se
Error: BitString is not a single exponential-Golomb code.
.. attribute:: Bits.ue
:type: int
Property for the unsigned exponential-Golomb code representation of the bitstring.
When used as a getter an :exc:`InterpretError` will be raised if the bitstring is not a single code.
.. attribute:: Bits.sie
:type: int
Property for the signed interleaved exponential-Golomb code representation of the bitstring.
When used as a getter an :exc:`InterpretError` will be raised if the bitstring is not a single code.
.. attribute:: Bits.uie
:type: int
Property for the unsigned interleaved exponential-Golomb code representation of the bitstring.
When used as a getter an :exc:`InterpretError` will be raised if the bitstring is not a single code.
.. attribute:: Bits.uint
:type: int
.. attribute:: Bits.u
:type: int
:noindex:
Property for the unsigned base-2 integer representation of the bitstring.
.. attribute:: Bits.uintbe
:type: int
Property for the byte-wise big-endian unsigned base-2 integer representation of the bitstring.
.. attribute:: Bits.uintle
:type: int
Property for the byte-wise little-endian unsigned base-2 integer representation of the bitstring.
.. attribute:: Bits.uintne
:type: int
Property for the byte-wise native-endian unsigned base-2 integer representation of the bitstring.
Special Methods
---------------
.. method:: Bits.__add__(bs)
.. method:: Bits.__radd__(bs)
``s1 + s2``
Concatenate two bitstring objects and return the result. Either bitstring can be 'auto' initialised. ::
s = Bits(ue=132) + '0xff'
s2 = '0b101' + s
.. method:: Bits.__and__(bs)
.. method:: Bits.__rand__(bs)
``s1 & s2``
Returns the bit-wise AND between two bitstrings, which must have the same length otherwise a :exc:`ValueError` is raised. ::
>>> print(Bits('0x33') & '0x0f')
0x03
.. method:: Bits.__bool__()
``if s:``
Returns ``False`` if the bitstring is empty (has zero length), otherwise returns ``True``.
>>> bool(Bits())
False
>>> bool(Bits('0b0000010000'))
True
>>> bool(Bits('0b0000000000'))
True
.. method:: Bits.__contains__(bs)
``bs in s``
Returns ``True`` if *bs* can be found in the bitstring, otherwise returns ``False``.
Similar to using :meth:`~Bits.find`, except that you are only told if it is found, and not where it was found. ::
>>> '0b11' in Bits('0x06')
True
>>> '0b111' in Bits('0x06')
False
.. method:: Bits.__copy__()
``s2 = copy.copy(s1)``
This allows the ``copy`` module to correctly copy bitstrings. Other equivalent methods are to initialise a new bitstring with the old one or to take a complete slice. ::
>>> import copy
>>> s = Bits('0o775')
>>> s_copy1 = copy.copy(s)
>>> s_copy2 = Bits(s)
>>> s_copy3 = s[:]
>>> s == s_copy1 == s_copy2 == s_copy3
True
.. method:: Bits.__eq__(bs)
``s1 == s2``
Compares two bitstring objects for equality, returning ``True`` if they have the same binary representation, otherwise returning ``False``. ::
>>> Bits('0o7777') == '0xfff'
True
>>> a = Bits(uint=13, length=8)
>>> b = Bits(uint=13, length=10)
>>> a == b
False
If you have a different criterion you wish to use then code it explicitly, for example ``a.int == b.int`` could be true even if ``a == b`` wasn't (as they could be different lengths).
.. method:: Bits.__getitem__(key)
``s[start:end:step]``
Returns a slice of the bitstring.
The usual slice behaviour applies. ::
>>> s = Bits('0x0123456')
>>> s[4:8]
Bits('0x1')
>>> s[1::8] # 1st, 9th, 17th and 25th bits
Bits('0x3')
If a single element is asked for then either ``True`` or ``False`` will be returned. ::
>>> s[0]
False
>>> s[-1]
True
.. method:: Bits.__hash__()
``hash(s)``
Returns an integer hash of the :class:`Bits`.
This method is not available for the :class:`BitArray` or :class:`BitStream` classes, as only immutable objects should be hashed. You typically won't need to call it directly, instead it is used for dictionary keys and in sets.
.. method:: Bits.__invert__()
``~s``
Returns the bitstring with every bit inverted, that is all zeros replaced with ones, and all ones replaced with zeros.
If the bitstring is empty then an :exc:`Error` will be raised. ::
>>> s = ConstBitStream(‘0b1110010’)
>>> print(~s)
0b0001101
>>> print(~s & s)
0b0000000
>>> ~~s == s
True
.. method:: Bits.__len__()
``len(s)``
Returns the length of the bitstring in bits.
If you are using a 32-bit Python build (which is quite unlikely these days) it's recommended that you use the :attr:`len` property rather than the :func:`len` function because of the function will raise a :exc:`OverflowError` if the length is greater than ``sys.maxsize``.
.. method:: Bits.__lshift__(n)
``s << n``
Returns the bitstring with its bits shifted *n* places to the left. The *n* right-most bits will become zeros. ::
>>> s = Bits('0xff')
>>> s << 4
Bits('0xf0')
.. method:: Bits.__mul__(n)
.. method:: Bits.__rmul__(n)
``s * n / n * s``
Return bitstring consisting of *n* concatenations of another. ::
>>> a = Bits('0x34')
>>> b = a*5
>>> print(b)
0x3434343434
.. method:: Bits.__ne__(bs)
``s1 != s2``
Compares two bitstring objects for inequality, returning ``False`` if they have the same binary representation, otherwise returning ``True``.
.. method:: Bits.__nonzero__()
See :meth:`__bool__`.
.. method:: Bits.__or__(bs)
.. method:: Bits.__ror__(bs)
``s1 | s2``
Returns the bit-wise OR between two bitstring, which must have the same length otherwise a :exc:`ValueError` is raised. ::
>>> print(Bits('0x33') | '0x0f')
0x3f
.. method:: Bits.__repr__()
``repr(s)``
A representation of the bitstring that could be used to create it (which will often not be the form used to create it).
If the result is too long then it will be truncated with ``...`` and the length of the whole will be given. ::
>>> Bits(‘0b11100011’)
Bits(‘0xe3’)
.. method:: Bits.__rshift__(n)
``s >> n``
Returns the bitstring with its bits shifted *n* places to the right. The *n* left-most bits will become zeros. ::
>>> s = Bits(‘0xff’)
>>> s >> 4
Bits(‘0x0f’)
.. method:: Bits.__str__()
``print(s)``
Used to print a representation of the bitstring, trying to be as brief as possible.
If the bitstring is a multiple of 4 bits long then hex will be used, otherwise either binary or a mix of hex and binary will be used. Very long strings will be truncated with ``...``. ::
>>> s = Bits('0b1')*7
>>> print(s)
0b1111111
>>> print(s + '0b1')
0xff
See also the :meth:`pp` method for ways to pretty-print the bitstring.
.. method:: Bits.__xor__(bs)
.. method:: Bits.__rxor__(bs)
``s1 ^ s2``
Returns the bit-wise XOR between two bitstrings, which must have the same length otherwise a :exc:`ValueError` is raised. ::
>>> print(Bits('0x33') ^ '0x0f')
0x3c
bitstring-bitstring-4.1.4/doc/bitstream.rst 0000664 0000000 0000000 00000002407 14531676336 0021042 0 ustar 00root root 0000000 0000000 .. currentmodule:: bitstring
BitStream
=========
.. class:: BitStream(__auto: BitsType | int | None, length: int | None = None, offset: int | None = None, pos: int = 0, **kwargs)
Both the :class:`BitArray` and the :class:`ConstBitStream` classes are base classes for :class:`BitStream` and so all of their methods are also available for :class:`BitStream` objects. The initialiser is the same as for :class:`ConstBitStream`.
A :class:`BitStream` is a mutable container of bits with methods and properties that allow it to be parsed as a stream of bits. There are no additional methods or properties in this class - see its base classes (:class:`Bits`, :class:`BitArray` and :class:`ConstBitStream`) for details.
The ``pos`` will also used as a default for the :meth:`BitArray.overwrite` and :meth:`BitArray.insert` methods.
The bit position is modifed by methods that read bits, as described in :attr:`~ConstBitStream.pos`, but for the mutable ``BitStream`` it is also modified by other methods:
* If a methods extends the bitstring (``+=``, ``append``) the ``pos`` will move to the end of the bitstring.
* If a method otherwise changes the length of the bitstring (``prepend``, ``insert``, sometimes ``replace``) the ``pos`` becomes invalid and will be reset to ``0``.
bitstring-bitstring-4.1.4/doc/bitstring_logo.png 0000664 0000000 0000000 00000075414 14531676336 0022061 0 ustar 00root root 0000000 0000000 PNG
IHDR |> zTXtRaw profile type exif xڭiv#9s{}9|%jSO"E# ws[_].W//
t7~_^w+F^_>Yt_`ؿ}E}_|>oa2.l$ܨ.\'xRH1K'!U,ZUkl.:8݃Ƙqgu٧ͱʫn;]w}'t)v3.P[nkwZpw-|t->ҁWxkkѱo !:4JdEAXnջwK\osNotΩuCfiUT?P-;ҷ61N`)B#rǓO$vW{[BBJhkjv>nur2TSRsS6wuyO]=5q:vZ[O۟ڙ4;xf`ۺ9!1oH$c,?74z:oKiW
cMkt vM3xrg41Upjҗg,4Xd̳&**@qb9gվCa\w4pYJn'L ;vcQe5qzJZFdk07cmvW9jqԼEKNSdOh+2]&:z[qRsWmmp.8~oJ]ﮫ3Ga|lyڞBW(=^i::\`Hhmrk1wtcc
cs2bА2ӈ\FީGu\9vl`y5UŦ `Y^mQ.p¤ksŶ
uR
3v
6Tl^gCAI1X8iՓF‹rlml
bmǂ2&Rس՞\i(2zt.P_+~c\m9Zq^\^dPCN
l9l
ӳƂTy2iJÞQEھ.VyogY+;#*u
zt\21TRCe4x%vA93e0ZQN-K5α2pt49oN |^UI[&N.s4%PLrgAI~fpEg8Eyb/Ƥܡg+eĮA=Nfݹ"P
ܬL:'
X8SfLJ[@iEM1L+<J3z>9GNͬ2$ځL)UȜ hh˜pJN}$xD4oɃGas7a3
2u:p4+·gmp
F@1&DUR[m*g "*)ˉ5Ol2F!
<-SׯXSU2
^bp̌Bv@x`Sl@95B>&S )0)3RH)nz*'/[wY`ދ#|Z&!Wa2/2ؐC0fNF`XvYDP)B-FozG)`j=Q!xOaKR2[wlwK<5Yv+ZX|CCpY)s.c\2"u2lU{-V
&-eƲAn%=KbLfF>3q儖lbTTo_2Mu*lK1.6|?#4F6SapN+q.1q%I6]χ驗m7?淈]FfXɢ]UeJlayQwrj2dեJ,nfClI/-x>UiA~
[!e+UfuZ
3f|h=ƷF%u(6(*RP(Vn1D T/sFP,gQDB~1Nj(SXC /ߘ<3/o!yCv5YI _/ ZDgJy3pD!Y:U2\L
"%,^QmRYl6߯1mP"n%D6H0F5=amjz
4W@l{CàG a&Nf?9I1˻DI):E$
)|m1' Q7oD JvB讠<ލ&b
pCI4Pw_=z /^OPʖ+*{oڅxY M!=_MZicKdҬaOpq,*boҔӐ *\~yHd@FXg1j'TrdȉǷCBoF\
ܫy<