bitarray 3.12.2__cp313-cp313-pyemscripten_2025_0_wasm32.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
bitarray/bitfields.py ADDED
@@ -0,0 +1,447 @@
1
+ # Copyright (c) 2026, Ilan Schnell; All Rights Reserved
2
+ # bitarray is published under the PSF license.
3
+ #
4
+ # Author: Ilan Schnell
5
+ """
6
+ Pack and unpack bit-level structures using `bitarray`.
7
+
8
+ https://github.com/ilanschnell/bitarray/blob/master/doc/bitfields.rst
9
+ """
10
+ import re
11
+ import struct
12
+ import operator
13
+ import functools
14
+ import collections
15
+ from dataclasses import dataclass
16
+ from typing import Any, Tuple
17
+
18
+ from bitarray import bitarray
19
+ from bitarray.util import int2ba, ba2int, hex2ba, ba2hex
20
+
21
+
22
+ __all__ = ["Struct", "compile", "pack", "pack_into", "unpack", "unpack_from",
23
+ "calcsize", "PaddingError"]
24
+
25
+
26
+ DEFAULT_ENDIAN = "little"
27
+
28
+ _ENDIAN_FROM_PREFIX = {"<": "little", ">": "big"}
29
+ _PREFIX_FROM_ENDIAN = {v: k for k, v in _ENDIAN_FROM_PREFIX.items()}
30
+
31
+
32
+ @dataclass(frozen=True)
33
+ class _Field:
34
+
35
+ width: int
36
+ endian: str
37
+ name: str
38
+ has_value = True
39
+ code = ""
40
+
41
+ @property
42
+ def prefix(self):
43
+ return _PREFIX_FROM_ENDIAN[self.endian]
44
+
45
+ def format(self):
46
+ res = "%s%s%d" % (self.prefix, self.code, self.width)
47
+ if self.name:
48
+ res += "{%s}" % self.name
49
+ return res
50
+
51
+
52
+ @dataclass(frozen=True)
53
+ class _IntField(_Field):
54
+
55
+ signed: bool
56
+
57
+ @property
58
+ def code(self):
59
+ return "s" if self.signed else "u"
60
+
61
+ def pack(self, value):
62
+ return int2ba(value, length=self.width, endian=self.endian,
63
+ signed=self.signed)
64
+
65
+ def unpack(self, a):
66
+ return ba2int(a, signed=self.signed)
67
+
68
+
69
+ @dataclass(frozen=True)
70
+ class _BoolField(_Field):
71
+
72
+ code = "?"
73
+
74
+ def __post_init__(self):
75
+ if self.width != 1:
76
+ raise ValueError("bool field width must be 1, "
77
+ "got %d" % self.width)
78
+
79
+ def pack(self, value):
80
+ return bitarray("1" if value else "0")
81
+
82
+ def unpack(self, a):
83
+ return bool(a[0])
84
+
85
+ def format(self):
86
+ res = "%s?" % self.prefix
87
+ if self.name:
88
+ res += "{%s}" % self.name
89
+ return res
90
+
91
+
92
+ @dataclass(frozen=True)
93
+ class _FloatField(_Field):
94
+
95
+ code = "f"
96
+ formats = {16: "e", 32: "f", 64: "d"}
97
+
98
+ def __post_init__(self):
99
+ if self.width not in self.formats:
100
+ raise ValueError("float field width must be 16, 32 or 64, "
101
+ "got %d" % self.width)
102
+
103
+ def struct_format(self):
104
+ return self.prefix + self.formats[self.width]
105
+
106
+ def pack(self, x):
107
+ return bitarray(struct.pack(self.struct_format(), x),
108
+ endian=self.endian)
109
+
110
+ def unpack(self, a):
111
+ return struct.unpack(self.struct_format(), bytes(a))[0]
112
+
113
+
114
+ @dataclass(frozen=True)
115
+ class _HexField(_Field):
116
+
117
+ code = "h"
118
+
119
+ def __post_init__(self):
120
+ if self.width % 4:
121
+ raise ValueError("hexadecimal field width must be a "
122
+ "multiple of 4, got %d" % self.width)
123
+
124
+ def pack(self, hexstr):
125
+ if not isinstance(hexstr, str):
126
+ raise TypeError("str expected, got %r" % type(hexstr).__name__)
127
+ a = hex2ba(hexstr, self.endian)
128
+ if len(a) != self.width:
129
+ raise ValueError("expected hex string of length %d, got %d" %
130
+ (self.width // 4, len(a) // 4))
131
+ return a
132
+
133
+ def unpack(self, a):
134
+ return ba2hex(a)
135
+
136
+
137
+ @dataclass(frozen=True)
138
+ class _BitarrayField(_Field):
139
+
140
+ code = "b"
141
+
142
+ def pack(self, a):
143
+ if not isinstance(a, bitarray):
144
+ raise TypeError("bitarray expected, got %r" % type(a).__name__)
145
+ if len(a) != self.width:
146
+ raise ValueError("expected bitarray of length %d, got %d" %
147
+ (self.width, len(a)))
148
+ return a
149
+
150
+ def unpack(self, a):
151
+ return a
152
+
153
+
154
+ @dataclass(frozen=True)
155
+ class _BytesField(_Field):
156
+
157
+ code = "B"
158
+
159
+ def __post_init__(self):
160
+ if self.width % 8:
161
+ raise ValueError("bytes field width must be a "
162
+ "multiple of 8, got %d" % self.width)
163
+
164
+ def pack(self, b):
165
+ if not isinstance(b, (bytes, bytearray)):
166
+ raise TypeError("bytes expected, got %r" % type(b).__name__)
167
+ if len(b) != self.width // 8:
168
+ raise ValueError("expected bytes of length %d, got %d" %
169
+ (self.width // 8, len(b)))
170
+ return bitarray(b, endian=self.endian)
171
+
172
+ def unpack(self, a):
173
+ return bytes(a)
174
+
175
+
176
+ class PaddingError(ValueError):
177
+ "Raised when validated padding bits do not match."
178
+
179
+
180
+ @dataclass(frozen=True)
181
+ class _PaddingField(_Field):
182
+ # For padding fields, the inherited name attribute stores the canonical
183
+ # explicit bit pattern, or "" when the field uses repeated padding.
184
+
185
+ value: bool
186
+ validate: bool
187
+ has_value = False
188
+
189
+ @classmethod
190
+ def from_code(cls, code, width, endian, name):
191
+ if name:
192
+ a = bitarray(name) # validates and ignores underscores
193
+ if width != len(a):
194
+ raise ValueError("pad-bits width %d does not match pattern "
195
+ "length %d" % (width, len(a)))
196
+ if (not a[0]) in a: # we have both 0 and 1 in a
197
+ value, name = False, a.to01()
198
+ else:
199
+ value, name = bool(a[0]), ""
200
+ else:
201
+ value = code.isupper()
202
+ return cls(width, endian, name, value=value,
203
+ validate=(code.lower() == "p"))
204
+
205
+ @property
206
+ def code(self):
207
+ return ("pP" if self.validate else "xX")[self.value]
208
+
209
+ @property
210
+ def bits(self):
211
+ return bitarray(self.name or self.width * ("01"[self.value]))
212
+
213
+ def pack(self, unused_value):
214
+ return self.bits
215
+
216
+ def unpack(self, a):
217
+ if self.validate and a != self.bits:
218
+ raise PaddingError("expected pad-bits %s, got %s" %
219
+ (self.bits.to01(), a.to01()))
220
+
221
+
222
+ @dataclass(frozen=True)
223
+ class Struct:
224
+ """Struct(format) -> compiled struct object
225
+
226
+ Central class for packing and unpacking bit-level structures.
227
+ """
228
+ _fields: tuple
229
+ width: int
230
+ values: int
231
+ _names: tuple
232
+
233
+ def __init__(self, format: str) -> None:
234
+ fields = self._fields_from_format(format)
235
+ object.__setattr__(self, "_fields", tuple(fields))
236
+ object.__setattr__(self, "width", sum(f.width for f in fields))
237
+ object.__setattr__(self, "values", sum(f.has_value for f in fields))
238
+ object.__setattr__(self, "_names", self._get_names())
239
+
240
+ def _get_names(self):
241
+ names = tuple(f.name for f in self._fields if f.has_value)
242
+ if not any(names):
243
+ return ()
244
+ if not all(names):
245
+ raise ValueError("Some but not all fields have a name")
246
+ self._result_type(names) # validate and warm cache
247
+ return names
248
+
249
+ @staticmethod
250
+ @functools.lru_cache()
251
+ def _result_type(names):
252
+ return collections.namedtuple("Unpacked", names)
253
+
254
+ _pat = re.compile(r"""
255
+ ([<>])? # optional prefix
256
+ ([a-zA-Z?]) # code character
257
+ (\d+)? # optional bit width
258
+ (?:\{(\w+)\})? # optional name (or padding bit pattern)
259
+ \s* # optional whitespace
260
+ """, re.VERBOSE | re.ASCII)
261
+
262
+ def _fields_from_format(self, format):
263
+ endian = DEFAULT_ENDIAN
264
+ fields = []
265
+ format = re.sub(r"#[^\r\n]*", "", format) # strip comments
266
+ format = format.lstrip()
267
+ while format:
268
+ m = self._pat.match(format)
269
+ if m is None:
270
+ raise ValueError("invalid format: %r" % format)
271
+ pre = m.group(1)
272
+ if pre:
273
+ endian = _ENDIAN_FROM_PREFIX[pre]
274
+ code = m.group(2)
275
+ name = m.group(4) or ""
276
+ width = int(m.group(3) or self._default_width(code, name))
277
+ if width == 0:
278
+ raise ValueError("field width cannot be zero: %r" % format)
279
+ fields.append(self._field_from_code(code, width, endian, name))
280
+ format = format[m.end():]
281
+ return fields
282
+
283
+ @staticmethod
284
+ def _default_width(code, name):
285
+ if code in "pPxX" and name:
286
+ # Underscores do not contribute to the width.
287
+ return len(name) - name.count("_")
288
+ return {"h": 4, "B": 8}.get(code, 1)
289
+
290
+ @staticmethod
291
+ def _field_from_code(code, width, endian, name):
292
+ if code in "us":
293
+ return _IntField(width, endian, name, signed=(code == "s"))
294
+ if code == "?":
295
+ return _BoolField(width, endian, name)
296
+ if code == "f":
297
+ return _FloatField(width, endian, name)
298
+ if code == "h":
299
+ return _HexField(width, endian, name)
300
+ if code == "b":
301
+ return _BitarrayField(width, endian, name)
302
+ if code == "B":
303
+ return _BytesField(width, endian, name)
304
+ if code in "pPxX":
305
+ return _PaddingField.from_code(code, width, endian, name)
306
+ raise ValueError("invalid code: %r" % code)
307
+
308
+ def format(self) -> str:
309
+ """format() -> str
310
+
311
+ Return the canonical format string reconstructed from this compiled format.
312
+ """
313
+ return " ".join(field.format() for field in self._fields)
314
+
315
+ def __repr__(self):
316
+ return "Struct(%r)" % self.format()
317
+
318
+ def __reduce__(self):
319
+ return Struct, (self.format(),)
320
+
321
+ def _normalize_offset(self, length, offset, op):
322
+ i = operator.index(offset)
323
+ if i < 0:
324
+ i += length
325
+ if not 0 <= i <= length:
326
+ raise ValueError("offset %d out of range for bitarray size %d" %
327
+ (offset, length))
328
+ if i + self.width > length:
329
+ raise ValueError(
330
+ "bitarray of at least %d bits required for %sing %d bits "
331
+ "at offset %d (actual bitarray size is %d)" %
332
+ (i + self.width, op, self.width, i, length))
333
+ return i
334
+
335
+ def pack(self, *values: Any) -> bitarray:
336
+ """pack(v1, v2, ...) -> bitarray
337
+
338
+ Return a bitarray containing the values v1, v2, ... packed according to this
339
+ compiled format.
340
+ """
341
+ if len(values) != self.values:
342
+ raise ValueError("expected %d values to pack, got %d" %
343
+ (self.values, len(values)))
344
+ value_iter = iter(values)
345
+ fields = self._fields
346
+ a = bitarray(0, fields[0].endian if fields else DEFAULT_ENDIAN)
347
+ for field in fields:
348
+ value = next(value_iter) if field.has_value else None
349
+ a.extend(field.pack(value))
350
+ return a
351
+
352
+ def pack_into(self, a: bitarray, offset: int, *values: Any) -> None:
353
+ """pack_into(bitarray, offset, v1, v2, ...) -> None
354
+
355
+ Pack the values v1, v2, ... into the writable bitarray starting at bit
356
+ offset `offset`. A negative offset counts from the end of bitarray.
357
+ """
358
+ if not isinstance(a, bitarray):
359
+ raise TypeError("bitarray expected, got %r" % type(a).__name__)
360
+ if a.readonly:
361
+ raise TypeError("cannot pack into read-only bitarray")
362
+ offset = self._normalize_offset(len(a), offset, "pack")
363
+ a[offset:offset + self.width] = self.pack(*values)
364
+
365
+ def _unpack(self, a, i=0):
366
+ values = []
367
+ for field in self._fields:
368
+ j = i + field.width
369
+ # Copy logical bits into endianness required by this field.
370
+ value = field.unpack(bitarray(a[i:j], field.endian))
371
+ if field.has_value:
372
+ values.append(value)
373
+ i = j
374
+ if self._names:
375
+ return self._result_type(self._names)(*values)
376
+ return tuple(values)
377
+
378
+ def unpack(self, a: bitarray) -> Tuple[Any, ...]:
379
+ """unpack(bitarray) -> tuple
380
+
381
+ Return a tuple containing values unpacked according to this compiled format.
382
+ """
383
+ if not isinstance(a, bitarray):
384
+ raise TypeError("bitarray expected, got %r" % type(a).__name__)
385
+ if len(a) != self.width:
386
+ raise ValueError("expected bitarray of length %d to unpack, "
387
+ "got %d" % (self.width, len(a)))
388
+ return self._unpack(a)
389
+
390
+ def unpack_from(self, a: bitarray, offset: int = 0) -> Tuple[Any, ...]:
391
+ """unpack_from(bitarray, offset=0) -> tuple
392
+
393
+ Unpack values from bitarray starting at bit offset `offset`, and return a
394
+ tuple. A negative offset counts from the end of bitarray.
395
+ """
396
+ if not isinstance(a, bitarray):
397
+ raise TypeError("bitarray expected, got %r" % type(a).__name__)
398
+ offset = self._normalize_offset(len(a), offset, "unpack")
399
+ return self._unpack(a, offset)
400
+
401
+
402
+ @functools.lru_cache()
403
+ def compile(format: str) -> Struct:
404
+ """compile(format) -> Struct
405
+
406
+ Compile given format string and return a compiled format object that
407
+ can be used to pack and/or unpack data multiple times.
408
+ """
409
+ return Struct(format)
410
+
411
+ def pack(format: str, *values: Any) -> bitarray:
412
+ """pack(format, v1, v2, ...) -> bitarray
413
+
414
+ Return a bitarray containing the values v1, v2, ... packed according
415
+ to the format string.
416
+ """
417
+ return compile(format).pack(*values)
418
+
419
+ def pack_into(format: str, a: bitarray, offset: int, *values: Any) -> None:
420
+ """pack_into(format, bitarray, offset, v1, v2, ...) -> None
421
+
422
+ Pack the values v1, v2, ... into the writable bitarray starting at bit
423
+ offset `offset`. A negative offset counts from the end of bitarray.
424
+ """
425
+ return compile(format).pack_into(a, offset, *values)
426
+
427
+ def unpack(format: str, a: bitarray) -> Tuple[Any, ...]:
428
+ """unpack(format, bitarray) -> tuple
429
+
430
+ Return a tuple containing values unpacked according to the format string.
431
+ """
432
+ return compile(format).unpack(a)
433
+
434
+ def unpack_from(format: str, a: bitarray, offset: int = 0) -> Tuple[Any, ...]:
435
+ """unpack_from(format, bitarray, offset=0) -> tuple
436
+
437
+ Unpack values from bitarray starting at bit offset `offset`, and return a
438
+ tuple. A negative offset counts from the end of bitarray.
439
+ """
440
+ return compile(format).unpack_from(a, offset)
441
+
442
+ def calcsize(format: str) -> int:
443
+ """calcsize(format) -> int
444
+
445
+ Return the size (in bits) of the struct corresponding to the format string.
446
+ """
447
+ return compile(format).width
bitarray/py.typed ADDED
File without changes