diff --git a/bitformat/__init__.py b/bitformat/__init__.py index a6f6727..5fef696 100644 --- a/bitformat/__init__.py +++ b/bitformat/__init__.py @@ -27,13 +27,14 @@ THE SOFTWARE. """ -__version__ = "0.0.1" +__version__ = "0.0.2" __author__ = "Scott Griffiths" from bitstring import Bits, Dtype, Array -from .format import Format, Field, Repeat, Find +from .field import Field, Find +from .format import Format, Repeat __all__ = ['Bits', 'Dtype', 'Format', 'Field', 'Array', 'Repeat', 'Find'] \ No newline at end of file diff --git a/bitformat/common.py b/bitformat/common.py new file mode 100644 index 0000000..c985cda --- /dev/null +++ b/bitformat/common.py @@ -0,0 +1,57 @@ +from __future__ import annotations +import sys +import ast + +indent_size = 4 + +class Colour: + def __new__(cls, use_colour: bool) -> Colour: + x = super().__new__(cls) + if use_colour: + cls.blue = '\033[34m' + cls.purple = '\033[35m' + cls.green = '\033[32m' + cls.red = '\033[31m' + cls.cyan = '\033[36m' + cls.off = '\033[0m' + else: + cls.blue = cls.purple = cls.green = cls.red = cls.cyan = cls.off = '' + return x + +is_interactive_shell = hasattr(sys, 'ps1') +colour = Colour(is_interactive_shell) + + +class Expression: + def __init__(self, code_str: str): + code_str = code_str.strip() + if code_str[0] != '{' or code_str[-1] != '}': + raise ValueError(f"Invalid expression: '{code_str}'. It should start with '{{' and end with '}}'.") + self.code_str = code_str[1:-1].strip() + self.code = self.compile_safe_eval() + self.value = None + + def compile_safe_eval(self): + # Only allowing operations for integer maths or boolean comparisons. + node_whitelist = {'BinOp', 'Name', 'Add', 'Expr', 'Mult', 'FloorDiv', 'Sub', 'Load', 'Module', 'Constant', + 'UnaryOp', 'USub', 'Mod', 'Pow', 'BitAnd', 'BitXor', 'BitOr', 'And', 'Or', 'BoolOp', 'LShift', + 'RShift', 'Eq', 'NotEq', 'Compare', 'LtE', 'GtE'} + nodes_used = set([x.__class__.__name__ for x in ast.walk(ast.parse(self.code_str))]) + bad_nodes = nodes_used - node_whitelist + if bad_nodes: + raise ValueError(f"Disallowed operations used in expression '{self.code_str}'. Disallowed nodes were: {bad_nodes}.") + if '__' in self.code_str: + raise ValueError(f"Invalid expression: '{self.code_str}'. Double underscores are not permitted.") + code = compile(self.code_str, "", "eval") + return code + + def safe_eval(self, vars_: Dict[str, Any]) -> Any: + self.value = eval(self.code, {"__builtins__": {}}, vars_) + return self.value + + def clear(self): + self.value = None + + def __str__(self): + value_str = '' if self.value is None else f' = {self.value}' + return f'{{{self.code_str}{value_str}}}' diff --git a/bitformat/field.py b/bitformat/field.py new file mode 100644 index 0000000..7fa9ab3 --- /dev/null +++ b/bitformat/field.py @@ -0,0 +1,353 @@ +from __future__ import annotations +import abc +from bitstring import Bits, Dtype, Array +from typing import Any, Tuple, List, Dict + +from .common import colour, Expression, indent_size + +class FieldType(abc.ABC): + @abc.abstractmethod + def _parse(self, b: Bits, vars_: Dict[str, Any]) -> int: + ... + + def parse(self, b: Bits) -> int: + return self._parse(b, {}) + + @abc.abstractmethod + def _build(self, values: List[Any], index: int, vars_: Dict[str, Any]) -> Tuple[Bits, int]: + ... + + def build(self, values: List[Any]) -> Bits: + return self._build(values, 0, {})[0] + + @abc.abstractmethod + def bits(self) -> Bits: + ... + + def bytes(self) -> bytes: + b = self.bits() + return b.tobytes() + + @abc.abstractmethod + def clear(self) -> None: + ... + + @abc.abstractmethod + def _str(self, indent: int) -> str: + ... + + @abc.abstractmethod + def flatten(self) -> List[FieldType]: + ... + + @abc.abstractmethod + def _getvalue(self) -> Any: + ... + + @abc.abstractmethod + def _setvalue(self, value: Any) -> None: + ... + + def __str__(self) -> str: + return self._str(0) + + def __repr__(self) -> str: + return self.__str__() + + def __eq__(self, other) -> bool: + return self.flatten() == other.flatten() + + def _get_name(self) -> str: + return self._name + + def _set_name(self, val: str) -> None: + if val != '': + if not val.isidentifier(): + raise ValueError(f"The FieldType name '{val}' is not a valid Python identifier.") + if '__' in val: + raise ValueError(f"The FieldType name '{val}' contains a double underscore which is not permitted.") + + self._name = val + + name = property(_get_name, _set_name) + + +class Field(FieldType): + def __init__(self, dtype: Dtype | Bits | str, name: str = '', value: Any = None, items: str | int = 1, const: bool | None = None) -> None: + self._bits = None + if isinstance(dtype, str): + d, n, v, i, c = Field._parse_dtype_str(dtype) + if n is not None: + if name != '': + raise ValueError( + f"A name was supplied in the formatted dtype '{dtype}' as well as in the name parameter.") + else: + name = n + if v is not None: + if value is not None: + raise ValueError( + f"A value was supplied in the formatted dtype '{dtype}' as well as in the value parameter.") + else: + value = v + if i != 1: + if items != 1: + raise ValueError(f"An multiplier was supplied in the formatted dtype '{dtype}' as well as in the items parameter.") + else: + items = i + if c is not None: + if const is not None and c is not const: + raise ValueError(f"A const value was supplied that conflicts with the formatted dtype '{dtype}'.") + else: + const = c + dtype = d + # Try to convert to Bits type first + try: + self._bits = Bits(dtype) + except ValueError: + try: + self.dtype = Dtype(dtype) + except ValueError: + raise ValueError(f"Can't convert '{dtype}' to either a Bits or a Dtype.") + else: + self.dtype = Dtype('bits', len(self._bits)) + value = self._bits + elif isinstance(dtype, Bits): + self.dtype = Dtype('bits', len(dtype)) + value = dtype + elif isinstance(dtype, Dtype): + self.dtype = dtype + else: + raise ValueError(f"Can't use '{dtype}' of type '{type(dtype)} to initialise Field.") + self.name = name + try: + self.items = int(items) + self.items_expression = None + except ValueError: + self.items_expression = Expression(items) + self.items = None + if self.dtype.length == 0: + raise ValueError(f"A field's dtype cannot have a length of zero (dtype = {self.dtype}).") + if const is None: + self.const = value is not None + else: + self.const = const + self.value = value + + def _parse(self, b: Bits, vars_: Dict[str, Any]) -> int: + if self.const: + value = b[:len(self._bits)] + if value != self._bits: + raise ValueError(f"Read value '{value}' when '{self._bits}' was expected.") + return len(self._bits) + if self.items_expression is not None: + self.items = self.items_expression.safe_eval(vars_) + if self.items == 1: + self._setvalue(self.dtype.get_fn(b[:self.dtype.bitlength])) + if self.name != '': + vars_[self.name] = self.value + return self.dtype.bitlength + else: + self._setvalue(b[:self.dtype.bitlength * self.items]) + return self.dtype.bitlength * self.items + + def _build(self, values: List[Any], index: int, vars_: Dict[str, Any]) -> Tuple[Bits, int]: + if self.const or self._bits: + return self._bits, 0 + if self.items_expression is not None: + self.items = self.items_expression.safe_eval(vars_) + self._setvalue(values[index]) + if self.name != '': + vars_[self.name] = self.value + return self._bits, 1 + + def bits(self) -> Bits: + return self._bits if self._bits is not None else Bits() + + def clear(self) -> None: + if not self.const: + self._setvalue(None) + if self.items_expression is not None: + self.items_expression.clear() + self.items = None + + def flatten(self) -> List[FieldType]: + return [self] + + @staticmethod + def _parse_dtype_str(dtype_str: str) -> Tuple[str, str | None, str | None, int, bool | None]: + # The string has the form 'dtype [* items] [] [= value]' + # But there may be chars inside {} sections that should be ignored. + # So we scan to find first real *, <, > and = + asterix_pos = -1 + lessthan_pos = -1 + greaterthan_pos = -1 + equals_pos = -1 + colon_pos = -1 + inside_braces = False + for pos, char in enumerate(dtype_str): + if char == '{': + if inside_braces: + raise ValueError(f"Two consecutive opening braces found in '{dtype_str}'.") + inside_braces = True + if char == '}': + if not inside_braces: + raise ValueError(f"Closing brace found with no matching opening brace in '{dtype_str}'.") + inside_braces = False + if inside_braces: + continue + if char == '*': + if asterix_pos != -1: + raise ValueError(f"More than one '*' found in '{dtype_str}'.") + asterix_pos = pos + if char == '<': + if lessthan_pos != -1: + raise ValueError(f"More than one '<' found in '{dtype_str}'.") + lessthan_pos = pos + if char == '>': + if greaterthan_pos != -1: + raise ValueError(f"More than one '>' found in '{dtype_str}'.") + greaterthan_pos = pos + if char == '=': + if equals_pos != -1: + raise ValueError(f"More than one '=' found in '{dtype_str}'.") + if colon_pos != -1: + raise ValueError(f"An '=' found in '{dtype_str}' as well as a ':'.") + equals_pos = pos + if char == ':': + if colon_pos != -1: + raise ValueError(f"More than one ':' found in '{dtype_str}'.") + if equals_pos != -1: + raise ValueError(f"A ':' found in '{dtype_str}' as well as an '='.") + colon_pos = pos + + name = value = const = None + items = 1 + # Check to see if it includes a value: + if equals_pos != -1: + value = dtype_str[equals_pos + 1:] + dtype_str = dtype_str[:equals_pos] + const = True + if colon_pos != -1: + value = dtype_str[colon_pos + 1:] + dtype_str = dtype_str[:colon_pos] + const = False + # Check if it has a name: + if lessthan_pos != -1: + if greaterthan_pos == -1: + raise ValueError( + f"An opening '<' was supplied in the formatted dtype '{dtype_str} but without a closing '>'.") + name = dtype_str[lessthan_pos + 1:greaterthan_pos] + name = name.strip() + chars_after_name = dtype_str[greaterthan_pos + 1:] + if chars_after_name != '' and not chars_after_name.isspace(): + raise ValueError(f"There should be no trailing characters after the .") + dtype_str = dtype_str[:lessthan_pos] + if asterix_pos != -1: + items = dtype_str[asterix_pos + 1:] + dtype_str = dtype_str[:asterix_pos] + return dtype_str, name, value, items, const + + def _getvalue(self) -> Any: + if self.items == 1: + return self._value + else: + return None if self._value is None else self._value + + def _setvalue(self, value: Any) -> None: + if self.dtype is None: + raise ValueError(f"Can't set a value for field without a Dtype.") + if value is None: + self._value = None + self._bits = None + return + if self.items == 1: + b = Bits() + try: + self.dtype.set_fn(b, value) + self._bits = b + except ValueError: + raise ValueError(f"Can't use the value '{value}' with the dtype {self.dtype}.") + self._value = self.dtype.get_fn(self._bits) + else: + a = Array(self.dtype, value) + if len(a) != self.items: + raise ValueError(f"For Field {self}, {len(a)} values were provided, but expected {self.items}.") + self._value = a + self._bits = a.data + + value = property(_getvalue, _setvalue) + + def _str(self, indent: int) -> str: + d = f"{colour.purple}{self.dtype}{colour.off}" + if self.items_expression is not None: + item_str = self.items_expression + else: + item_str = '' if self.items == 1 else str(self.items) + i = '' if item_str == '' else f" * {colour.purple}{item_str}{colour.off}" + n = '' if self.name == '' else f" <{colour.green}{self.name}{colour.off}>" + divider = '=' if self.const else ':' + if isinstance(self.value, Array): + v = f" {divider} {colour.cyan}{self.value.tolist()}{colour.off}" + else: + v = '' if self.value is None else f" {divider} {colour.cyan}{self.value}{colour.off}" + indent_str = ' ' * indent_size * indent + return f"{indent_str}'{d}{i}{n}{v}'" + + def __repr__(self) -> str: + return f"Field({self.__str__()})" + + def __eq__(self, other: Any) -> bool: + if self.dtype != other.dtype: + return False + if isinstance(self.value, Array): + if not isinstance(other.value, Array): + return False + if not self.value.equals(other.value): + return False + elif self.value != other.value: + return False + return True + + +class Find(FieldType): + + def __init__(self, bits: Bits | str, bytealigned: bool = True, name: str = ''): + super().__init__() + self.bits_to_find = Bits(bits) + self.bytealigned = bytealigned + self.name = name + + def _build(self, values: List[Any], index: int, _vars: Dict[str, Any]) -> Tuple[Bits, int]: + return Bits(), 0 + + def bits(self) -> Bits: + return Bits() + + def clear(self) -> None: + self._setvalue(None) + + def _parse(self, b: Bits, vars_: Dict[str, Any]) -> int: + p = b.find(self.bits_to_find, bytealigned=self.bytealigned) + if p: + self._setvalue(p[0]) + return p[0] + self._setvalue(None) + return 0 + + def flatten(self) -> List[FieldType]: + return [] + + def _str(self, indent: int) -> str: + indent_str = ' ' * indent_size * indent + name_str = '' if self.name == '' else f"'{colour.blue}{self.name}{colour.off}'," + find_str = f"'{colour.green}{str(self.bits_to_find)}{colour.off}'" + s = f"{indent_str}{self.__class__.__name__}({name_str}{find_str})" + return s + + def _setvalue(self, val: int | None) -> None: + self._value = val + + def _getvalue(self) -> int | None: + return self._value + + value = property(_getvalue, None) # Don't allow the value to be set elsewhere \ No newline at end of file diff --git a/bitformat/format.py b/bitformat/format.py index 751cecf..ca16053 100644 --- a/bitformat/format.py +++ b/bitformat/format.py @@ -1,369 +1,11 @@ from __future__ import annotations -from bitstring import Bits, Dtype, Array +from bitstring import Bits, Dtype from typing import Sequence, Any, Iterable, Tuple, List, Dict import copy -import ast -import abc -import sys - -class Colour: - def __new__(cls, use_colour: bool) -> Colour: - x = super().__new__(cls) - if use_colour: - cls.blue = '\033[34m' - cls.purple = '\033[35m' - cls.green = '\033[32m' - cls.red = '\033[31m' - cls.cyan = '\033[36m' - cls.off = '\033[0m' - else: - cls.blue = cls.purple = cls.green = cls.red = cls.cyan = cls.off = '' - return x - -is_interactive_shell = hasattr(sys, 'ps1') -colour = Colour(is_interactive_shell) -indent_size = 4 - - -class Expression: - def __init__(self, code_str: str): - code_str = code_str.strip() - if code_str[0] != '{' or code_str[-1] != '}': - raise ValueError(f"Invalid expression: '{code_str}'. It should start with '{{' and end with '}}'.") - self.code_str = code_str[1:-1].strip() - self.code = self.compile_safe_eval() - self.value = None - - def compile_safe_eval(self): - # Only allowing operations for integer maths or boolean comparisons. - node_whitelist = {'BinOp', 'Name', 'Add', 'Expr', 'Mult', 'FloorDiv', 'Sub', 'Load', 'Module', 'Constant', - 'UnaryOp', 'USub', 'Mod', 'Pow', 'BitAnd', 'BitXor', 'BitOr', 'And', 'Or', 'BoolOp', 'LShift', - 'RShift', 'Eq', 'NotEq', 'Compare', 'LtE', 'GtE'} - nodes_used = set([x.__class__.__name__ for x in ast.walk(ast.parse(self.code_str))]) - bad_nodes = nodes_used - node_whitelist - if bad_nodes: - raise ValueError(f"Disallowed operations used in expression '{self.code_str}'. Disallowed nodes were: {bad_nodes}.") - if '__' in self.code_str: - raise ValueError(f"Invalid expression: '{self.code_str}'. Double underscores are not permitted.") - code = compile(self.code_str, "", "eval") - return code - - def safe_eval(self, vars_: Dict[str, Any]) -> Any: - self.value = eval(self.code, {"__builtins__": {}}, vars_) - return self.value - - def clear(self): - self.value = None - - def __str__(self): - value_str = '' if self.value is None else f' = {self.value}' - return f'{{{self.code_str}{value_str}}}' - - -class FieldType(abc.ABC): - @abc.abstractmethod - def _parse(self, b: Bits, vars_: Dict[str, Any]) -> int: - ... - - def parse(self, b: Bits) -> int: - return self._parse(b, {}) - - @abc.abstractmethod - def _build(self, values: List[Any], index: int, vars_: Dict[str, Any]) -> Tuple[Bits, int]: - ... - - def build(self, values: List[Any]) -> Bits: - return self._build(values, 0, {})[0] - - @abc.abstractmethod - def bits(self) -> Bits: - ... - - def bytes(self) -> bytes: - b = self.bits() - return b.tobytes() - - @abc.abstractmethod - def clear(self) -> None: - ... - - @abc.abstractmethod - def _str(self, indent: int) -> str: - ... - - @abc.abstractmethod - def flatten(self) -> List[FieldType]: - ... - - @abc.abstractmethod - def _getvalue(self) -> Any: - ... - - @abc.abstractmethod - def _setvalue(self, value: Any) -> None: - ... - - def __str__(self) -> str: - return self._str(0) - - def __repr__(self) -> str: - return self.__str__() - - def __eq__(self, other): - return self.flatten() == other.flatten() - - def _get_name(self) -> str: - return self._name - - def _set_name(self, val: str) -> None: - if val != '': - if not val.isidentifier(): - raise ValueError(f"The FieldType name '{val}' is not a valid Python identifier.") - if '__' in val: - raise ValueError(f"The FieldType name '{val}' contains a double underscore which is not permitted.") - - self._name = val - - name = property(_get_name, _set_name) - - -class Field(FieldType): - def __init__(self, dtype: Dtype | Bits | str, name: str = '', value: Any = None, items: str | int = 1, const: bool | None = None) -> None: - self._bits = None - if isinstance(dtype, str): - d, n, v, i, c = Field._parse_dtype_str(dtype) - if n is not None: - if name != '': - raise ValueError( - f"A name was supplied in the formatted dtype '{dtype}' as well as in the name parameter.") - else: - name = n - if v is not None: - if value is not None: - raise ValueError( - f"A value was supplied in the formatted dtype '{dtype}' as well as in the value parameter.") - else: - value = v - if i != 1: - if items != 1: - raise ValueError(f"An multiplier was supplied in the formatted dtype '{dtype}' as well as in the items parameter.") - else: - items = i - if c is not None: - if const is not None and c is not const: - raise ValueError(f"A const value was supplied that conflicts with the formatted dtype '{dtype}'.") - else: - const = c - dtype = d - # Try to convert to Bits type first - try: - self._bits = Bits(dtype) - except ValueError: - try: - self.dtype = Dtype(dtype) - except ValueError: - raise ValueError(f"Can't convert '{dtype}' to either a Bits or a Dtype.") - else: - self.dtype = Dtype('bits', len(self._bits)) - value = self._bits - elif isinstance(dtype, Bits): - self.dtype = Dtype('bits', len(dtype)) - value = dtype - elif isinstance(dtype, Dtype): - self.dtype = dtype - else: - raise ValueError(f"Can't use '{dtype}' of type '{type(dtype)} to initialise Field.") - self.name = name - try: - self.items = int(items) - self.items_expression = None - except ValueError: - self.items_expression = Expression(items) - self.items = None - if self.dtype.length == 0: - raise ValueError(f"A field's dtype cannot have a length of zero (dtype = {self.dtype}).") - if const is None: - self.const = value is not None - else: - self.const = const - self.value = value - - def _parse(self, b: Bits, vars_: Dict[str, Any]) -> int: - if self.const: - value = b[:len(self._bits)] - if value != self._bits: - raise ValueError(f"Read value '{value}' when '{self._bits}' was expected.") - return len(self._bits) - if self.items_expression is not None: - self.items = self.items_expression.safe_eval(vars_) - if self.items == 1: - self._setvalue(self.dtype.get_fn(b[:self.dtype.bitlength])) - if self.name != '': - vars_[self.name] = self.value - return self.dtype.bitlength - else: - self._setvalue(b[:self.dtype.bitlength * self.items]) - return self.dtype.bitlength * self.items - - def _build(self, values: List[Any], index: int, vars_: Dict[str, Any]) -> Tuple[Bits, int]: - if self.const or self._bits: - return self._bits, 0 - if self.items_expression is not None: - self.items = self.items_expression.safe_eval(vars_) - self._setvalue(values[index]) - if self.name != '': - vars_[self.name] = self.value - return self._bits, 1 - - def bits(self) -> Bits: - return self._bits if self._bits is not None else Bits() - - def clear(self) -> None: - if not self.const: - self._setvalue(None) - if self.items_expression is not None: - self.items_expression.clear() - self.items = None - - def flatten(self) -> List[FieldType]: - return [self] - - @staticmethod - def _parse_dtype_str(dtype_str: str) -> Tuple[str, str | None, str | None, int, bool | None]: - # The string has the form 'dtype [* items] [] [= value]' - # But there may be chars inside {} sections that should be ignored. - # So we scan to find first real *, <, > and = - asterix_pos = -1 - lessthan_pos = -1 - greaterthan_pos = -1 - equals_pos = -1 - colon_pos = -1 - inside_braces = False - for pos, char in enumerate(dtype_str): - if char == '{': - if inside_braces: - raise ValueError(f"Two consecutive opening braces found in '{dtype_str}'.") - inside_braces = True - if char == '}': - if not inside_braces: - raise ValueError(f"Closing brace found with no matching opening brace in '{dtype_str}'.") - inside_braces = False - if inside_braces: - continue - if char == '*': - if asterix_pos != -1: - raise ValueError(f"More than one '*' found in '{dtype_str}'.") - asterix_pos = pos - if char == '<': - if lessthan_pos != -1: - raise ValueError(f"More than one '<' found in '{dtype_str}'.") - lessthan_pos = pos - if char == '>': - if greaterthan_pos != -1: - raise ValueError(f"More than one '>' found in '{dtype_str}'.") - greaterthan_pos = pos - if char == '=': - if equals_pos != -1: - raise ValueError(f"More than one '=' found in '{dtype_str}'.") - if colon_pos != -1: - raise ValueError(f"An '=' found in '{dtype_str}' as well as a ':'.") - equals_pos = pos - if char == ':': - if colon_pos != -1: - raise ValueError(f"More than one ':' found in '{dtype_str}'.") - if equals_pos != -1: - raise ValueError(f"A ':' found in '{dtype_str}' as well as an '='.") - colon_pos = pos - - name = value = const = None - items = 1 - # Check to see if it includes a value: - if equals_pos != -1: - value = dtype_str[equals_pos + 1:] - dtype_str = dtype_str[:equals_pos] - const = True - if colon_pos != -1: - value = dtype_str[colon_pos + 1:] - dtype_str = dtype_str[:colon_pos] - const = False - # Check if it has a name: - if lessthan_pos != -1: - if greaterthan_pos == -1: - raise ValueError( - f"An opening '<' was supplied in the formatted dtype '{dtype_str} but without a closing '>'.") - name = dtype_str[lessthan_pos + 1:greaterthan_pos] - name = name.strip() - chars_after_name = dtype_str[greaterthan_pos + 1:] - if chars_after_name != '' and not chars_after_name.isspace(): - raise ValueError(f"There should be no trailing characters after the .") - dtype_str = dtype_str[:lessthan_pos] - if asterix_pos != -1: - items = dtype_str[asterix_pos + 1:] - dtype_str = dtype_str[:asterix_pos] - return dtype_str, name, value, items, const - - def _getvalue(self) -> Any: - if self.items == 1: - return self._value - else: - return None if self._value is None else self._value - - def _setvalue(self, value: Any) -> None: - if self.dtype is None: - raise ValueError(f"Can't set a value for field without a Dtype.") - if value is None: - self._value = None - self._bits = None - return - if self.items == 1: - b = Bits() - try: - self.dtype.set_fn(b, value) - self._bits = b - except ValueError: - raise ValueError(f"Can't use the value '{value}' with the dtype {self.dtype}.") - self._value = self.dtype.get_fn(self._bits) - else: - a = Array(self.dtype, value) - if len(a) != self.items: - raise ValueError(f"For Field {self}, {len(a)} values were provided, but expected {self.items}.") - self._value = a - self._bits = a.data - - value = property(_getvalue, _setvalue) - - def _str(self, indent: int) -> str: - d = f"{colour.purple}{self.dtype}{colour.off}" - if self.items_expression is not None: - item_str = self.items_expression - else: - item_str = '' if self.items == 1 else str(self.items) - i = '' if item_str == '' else f" * {colour.purple}{item_str}{colour.off}" - n = '' if self.name == '' else f" <{colour.green}{self.name}{colour.off}>" - divider = '=' if self.const else ':' - if isinstance(self.value, Array): - v = f" {divider} {colour.cyan}{self.value.tolist()}{colour.off}" - else: - v = '' if self.value is None else f" {divider} {colour.cyan}{self.value}{colour.off}" - indent_str = ' ' * indent_size * indent - return f"{indent_str}'{d}{i}{n}{v}'" - - def __repr__(self) -> str: - return f"Field({self.__str__()})" - - def __eq__(self, other: Any) -> bool: - if self.dtype != other.dtype: - return False - if isinstance(self.value, Array): - if not isinstance(other.value, Array): - return False - if not self.value.equals(other.value): - return False - elif self.value != other.value: - return False - return True +from .common import colour, indent_size +from .field import FieldType, Field class FieldListType(FieldType): @@ -494,48 +136,3 @@ def _str(self, indent: int) -> str: s += fieldtype._str(indent + 1) + ',\n' s += f"{indent_str})" return s - - - -class Find(FieldType): - - def __init__(self, bits: Bits | str, bytealigned: bool = True, name: str = ''): - super().__init__() - self.bits_to_find = Bits(bits) - self.bytealigned = bytealigned - self.name = name - - def _build(self, values: List[Any], index: int, _vars: Dict[str, Any]) -> Tuple[Bits, int]: - return Bits(), 0 - - def bits(self) -> Bits: - return Bits() - - def clear(self) -> None: - self._setvalue(None) - - def _parse(self, b: Bits, vars_: Dict[str, Any]) -> int: - p = b.find(self.bits_to_find, bytealigned=self.bytealigned) - if p: - self._setvalue(p[0]) - return p[0] - self._setvalue(None) - return 0 - - def flatten(self) -> List[FieldType]: - return [] - - def _str(self, indent: int) -> str: - indent_str = ' ' * indent_size * indent - name_str = '' if self.name == '' else f"'{colour.blue}{self.name}{colour.off}'," - find_str = f"'{colour.green}{str(self.bits_to_find)}{colour.off}'" - s = f"{indent_str}{self.__class__.__name__}({name_str}{find_str})" - return s - - def _setvalue(self, val: int | None) -> None: - self._value = val - - def _getvalue(self) -> int | None: - return self._value - - value = property(_getvalue, None) # Don't allow the value to be set elsewhere diff --git a/tests/test_field.py b/tests/test_field.py index b386480..f1ec78c 100644 --- a/tests/test_field.py +++ b/tests/test_field.py @@ -4,7 +4,7 @@ def setUpModule(): - bitformat.format.colour = bitformat.format.Colour(False) + bitformat.common.colour = bitformat.common.Colour(False) class Creation(unittest.TestCase): def testCreationFromDtype(self): diff --git a/tests/test_format.py b/tests/test_format.py index 2a8136a..ede568f 100644 --- a/tests/test_format.py +++ b/tests/test_format.py @@ -4,7 +4,7 @@ import bitformat def setUpModule(): - bitformat.format.colour = bitformat.format.Colour(False) + bitformat.common.colour = bitformat.common.Colour(False) class Creation(unittest.TestCase): @@ -111,7 +111,7 @@ def testExampleWithArray(self): Field('bytes', 'signature', b'BMP'), 'i8 ', 'i8 ', - 'u8 * 6 ', + 'u8 * {width * height} ', ], 'construct_example') b = f.build([3, 2, [7, 8, 9, 11, 12, 13]]) v = b'BMP\x03\x02\x07\x08\t\x0b\x0c\r'