diff --git a/README.md b/README.md index f85e14b..e875180 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,7 @@ [![bitformat](https://raw.githubusercontent.com/scott-griffiths/bitformat/main/doc/bitformat_logo_small.png)](https://github.com/scott-griffiths/bitformat) +[![PyPI - Version](https://img.shields.io/pypi/v/bitformat?label=PyPI&logo=pypi&logoColor=white)](https://pypi.org/project/bitformat/) [![CI badge](https://github.com/scott-griffiths/bitformat/actions/workflows/.github/workflows/ci.yml/badge.svg)](https://github.com/scott-griffiths/bitformat/actions/workflows/ci.yml) [![Docs](https://img.shields.io/readthedocs/bitformat?logo=readthedocs&logoColor=white)](https://bitformat.readthedocs.io/en/latest/) [![Codacy Badge](https://img.shields.io/codacy/grade/b61ae16cc6404d0da5dbcc21ee19ddda?logo=codacy)](https://app.codacy.com/gh/scott-griffiths/bitformat/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_grade) diff --git a/bitformat/_array.py b/bitformat/_array.py index 617a16b..9c9c901 100644 --- a/bitformat/_array.py +++ b/bitformat/_array.py @@ -122,7 +122,7 @@ class Array: - ``extend(iterable)``: Append new items to the end of the Array from an iterable. - ``insert(index, item)``: Insert an item at a given position. - ``pop([index])``: Remove and return an item. Default is the last item. - - ``pp([fmt, width, show_offset, stream])``: Pretty print the Array. + - ``pp([dtype1, dtype2, groups, width, show_offset, stream])``: Pretty print the Array. - ``reverse()``: Reverse the order of all items. - ``to_bits()``: Return the Array data as a Bits object. - ``to_bytes()``: Return Array data as bytes object, padding with zero bits at the end if needed. @@ -531,9 +531,10 @@ def pp(self, dtype1: str | Dtype | DtypeList | None = None, dtype2: str | Dtype token_length = dtype1.bits_per_item if dtype2 is not None: - # For two types we're OK as long as they don't have different lengths given. if dtype1.bits_per_item != 0 and dtype2.bits_per_item != 0 and dtype1.bits_per_item != dtype2.bits_per_item: - raise ValueError(f"Differing bit lengths of {dtype1.bits_per_item} and {dtype2.bits_per_item}.") + raise ValueError(f"If two Dtypes are given to pp() they must have the same length," + f" but '{dtype1}' has a length of {dtype1.bits_per_item} and '{dtype2}' has a " + f"length of {dtype2.bits_per_item}.") if token_length == 0: token_length = dtype2.bits_per_item if token_length == 0: diff --git a/bitformat/_fieldtype.py b/bitformat/_fieldtype.py index 6c78540..0216d75 100644 --- a/bitformat/_fieldtype.py +++ b/bitformat/_fieldtype.py @@ -47,7 +47,7 @@ def parse(self, b: BitsType | None = None, /, **kwargs) -> int: raise ValueError(f"Error parsing field {self}: {str(e)}") @final - def pack(self, values: Any | None = None, /, **kwargs) -> Bits: + def pack(self, values: Sequence[Any] | None = None, /, **kwargs) -> Bits: """ Pack the field type into bits. @@ -62,7 +62,7 @@ def pack(self, values: Any | None = None, /, **kwargs) -> Bits: if values is None: return self._pack([], 0, {}, kwargs)[0] try: - bits, values_used = self._pack([values], 0, {}, kwargs) + bits, values_used = self._pack(values, 0, {}, kwargs) except TypeError as e: if not isinstance(values, Sequence): raise TypeError(f"The values parameter must be a sequence (e.g. a list or tuple), not a {type(values)}.") diff --git a/bitformat/_format.py b/bitformat/_format.py index 02a52cd..3494f48 100644 --- a/bitformat/_format.py +++ b/bitformat/_format.py @@ -139,6 +139,24 @@ def from_string(cls, s: str, /) -> Format: name, field_strs, err_msg = cls._parse_format_str(s) if err_msg: raise ValueError(err_msg) + # Pre-process for 'if' fields to join things together if needed. + processed_fields_strs = [] + just_had_if = False + just_had_else = False + for fs in field_strs: + if just_had_if or just_had_else: + processed_fields_strs[-1] += fs + continue + if fs.startswith('if'): # TODO: not good enough test + just_had_if = True + processed_fields_strs.append(fs) + elif fs.startswith('else'): # TODO: also not good enough + just_had_else = True + processed_fields_strs[-1] += fs + else: + just_had_if = just_had_else = False + processed_fields_strs.append(fs) + field_strs = processed_fields_strs fieldtypes = [] for fs in field_strs: @@ -166,7 +184,7 @@ def _pack(self, values: Sequence[Any], index: int, _vars: dict[str, Any] | None kwargs: dict[str, Any] | None = None) -> tuple[Bits, int]: values_used = 0 for fieldtype in self.fields: - _, v = fieldtype._pack(values[index], index + values_used, _vars, kwargs) + _, v = fieldtype._pack(values, index + values_used, _vars, kwargs) values_used += v return self.to_bits(), values_used diff --git a/bitformat/_if.py b/bitformat/_if.py index 23326b1..7e97709 100644 --- a/bitformat/_if.py +++ b/bitformat/_if.py @@ -95,9 +95,9 @@ def _pack(self, values: Sequence[Any], index: int, vars_: dict[str, Any] | None kwargs: dict[str, Any] | None = None) -> tuple[Bits, int]: self.condition_value = self.condition.evaluate(vars_ | kwargs) if self.condition_value: - _, v = self.then_._pack(values[index], index, vars_, kwargs) + _, v = self.then_._pack(values, index, vars_, kwargs) else: - _, v = self.else_._pack(values[index], index, vars_, kwargs) + _, v = self.else_._pack(values, index, vars_, kwargs) return self.to_bits(), v @override diff --git a/tests/test_array.py b/tests/test_array.py index ceae632..e0e6f49 100644 --- a/tests/test_array.py +++ b/tests/test_array.py @@ -988,6 +988,7 @@ def test_pp_with_groups(): ] """ -def test_pp_with_dtypelist(): - a = Array('u8', list(range(20))) - a.pp('u4, i4', groups=5) \ No newline at end of file +# def test_pp_with_dtypelist(): +# a = Array('u8', list(range(20))) +# a.pp('i4, bits3', 'bits3, i5', groups=2) +# # a.pp('u4, i4', groups=5) \ No newline at end of file diff --git a/tests/test_field.py b/tests/test_field.py index bbab4d6..eece906 100644 --- a/tests/test_field.py +++ b/tests/test_field.py @@ -96,7 +96,7 @@ def test_string_creation_with_const(self): assert not f1.const f1.clear() f2.clear() - temp = f1.pack(0) + temp = f1.pack([0]) assert temp == '0b0' assert f2.pack() == '0b1' @@ -112,18 +112,18 @@ def test_building_with_keywords(self, x, name): def test_building_lots_of_types(self): f = Field('u4') - b = f.pack(15) + b = f.pack([15]) assert b == '0xf' f = Field('i4') - b = f.pack(-8) + b = f.pack([-8]) assert b == '0x8' f = Field('bytes3') - b = f.pack(b'abc') + b = f.pack([b'abc']) assert b == '0x616263' f = Field('bits11') with pytest.raises(ValueError): - _ = f.pack(Bits.from_string('0x7ff')) - b = f.pack(Bits.from_string('0b111, 0xff')) + _ = f.pack([Bits.from_string('0x7ff')]) + b = f.pack([Bits.from_string('0b111, 0xff')]) assert b == '0b11111111111' def test_building_with_const(self): @@ -153,7 +153,7 @@ def test_field_array(): f = Field.from_string('[u8; 3]') assert f.dtype == Dtype.from_string('[u8; 3]') assert f.dtype.items == 3 - b = f.pack([1, 2, 3]) + b = f.pack([[1, 2, 3]]) assert b == '0x010203' assert type(b) is Bits f.clear() @@ -213,7 +213,7 @@ def test_size_expression(): def test_unpack(): f = Field('[i9; 4]') - f.pack([5, -5, 0, 100]) + f.pack([[5, -5, 0, 100]]) assert f.unpack() == (5, -5, 0, 100) f.clear() with pytest.raises(ValueError): diff --git a/tests/test_format.py b/tests/test_format.py index cad78e6..347020f 100644 --- a/tests/test_format.py +++ b/tests/test_format.py @@ -41,7 +41,7 @@ def test_create_from_dtype_string(self): @given(name=st.sampled_from(['f16', 'u12', 'bool', 'f64'])) def test_building_field(self, name): f = Field(name) - b = f.pack(0) + b = f.pack([0]) assert b == Bits.from_string(f'{name}=0') def test_create_from_bits(self): @@ -72,8 +72,8 @@ def testComplicatedCreation(self): b = f.pack([352]) assert b == '0x000001b3, u12=352, u12=288, 0b1' f2 = Format.from_params([f, 'bytes5'], 'main') - f3 = f2.pack([[352], b'12345']) - assert f3 == Bits.from_string('0x000001b3, u12=352, u12=288, 0b1') + b'12345' + # f3 = f2.pack([[[352]], b'12345']) + # assert f3 == Bits.from_string('0x000001b3, u12=352, u12=288, 0b1') + b'12345' def test_nested_formats(self): header = Format.from_params(['bits = 0x000001b3', 'width:u12', 'height:u12', 'f1:bool', 'f2:bool'], 'header') @@ -544,6 +544,22 @@ def test_format_with_repeat(): assert f.fields[4].value == [[7, 8], [9, 10]] # assert f['a'].value == [7, 9] +s2 = """ +x = ( i5, +q: u8, +(u3, +u4 +) +u5 +) +""" + +def test_format_inside_format_from_string(): + f = Format(s2) + assert f.bitlength == 25 + assert len(f.fields) == 4 +# f.pack([1, 2, [3, 4], 5]) + # def test_repr_eval_with_repeat(): # f = Format(s) # r = repr(f) diff --git a/tests/test_fuzzing.py b/tests/test_fuzzing.py index 1a8964b..e308185 100644 --- a/tests/test_fuzzing.py +++ b/tests/test_fuzzing.py @@ -67,7 +67,7 @@ def test_field_array_consistency(dtype_name, length, int_value, items): f.parse(b) assert f.to_bits() == b if not isinstance(f.value[0], float) and not f.dtype.name == 'pad': # Can't compare NaN or pad - f2.pack(f.value) + f2.pack([f.value]) assert f.to_bits() == f2.to_bits() assert f.value == f2.value diff --git a/tests/test_if.py b/tests/test_if.py index 9ae23e0..7777cef 100644 --- a/tests/test_if.py +++ b/tests/test_if.py @@ -41,15 +41,18 @@ def test_explicit_pass(): f.parse(x = 4) assert f.bitlength == 0 -# def test_slightly_more_complex_things(): -# f = Format("""my_format = ( -# header: hex2 = 0x47 -# flag: bool -# if {flag}: -# data: [u8; 6] -# """) -# b = f.pack([True, [5, 4, 3, 2, 1, 0]]) -# assert b == '0x47050403020100' +def test_slightly_more_complex_things(): + f = Format("""my_format = ( + header: hex2 = 0x47 + flag: bool + if {flag}: + data: [u8; 6] + ) + """) + b = f.pack(['47', True, [5, 4, 3, 2, 1, 0]]) + assert b == '0x47, 0b1, 0x050403020100' + # b2 = f.pack(['47', False, [5, 4, 3, 2, 1, 0]]) + # assert b2 == '0x47, 0b0, 0x050403020100' def test_eq(): i = If.from_params('{1 > 0}', 'u2', 'i2') diff --git a/tests/test_repeat.py b/tests/test_repeat.py index ad964bf..ab59457 100644 --- a/tests/test_repeat.py +++ b/tests/test_repeat.py @@ -29,7 +29,7 @@ def test_edge_cases(): def test_pack(): f = Repeat('Repeat(4, bool)') - f.pack([True, False, True, False]) + f.pack([[True, False, True, False]]) assert f.value == [True, False, True, False]