Source code for struct_parse
"""Parse the format defined in the :py:mod:`struct` standard library module.
Single-file parser for the string format specified by the :py:mod:`struct`
standard library module. This enables the use of strings describing the layout
of packed binary data for other things besides just packing and unpacking, like
generating C struct definitions.
"""
from typing import List
import enum
[docs]class ByteOrder(enum.Enum):
NATIVE = 0
LITTLE = 1
BIG = 2
NETWORK = 3
BYTE_ORDER = {
'@': ByteOrder.NATIVE,
'=': ByteOrder.NATIVE,
'<': ByteOrder.LITTLE,
'>': ByteOrder.BIG,
'!': ByteOrder.NETWORK,
}
ORDER_STRING = {}
for k, v in BYTE_ORDER.items():
ORDER_STRING[v] = k
ORDER_STRING[ByteOrder.NATIVE] = '@'
[docs]class FieldType(enum.Enum):
"""Type of a field in the struct. Represented by a single character in the
format string.
"""
PAD = 0
CHAR = 1
SIGNED_CHAR = 2
UNSIGNED_CHAR = 3
BOOL = 4
SHORT = 5
UNSIGNED_SHORT = 6
INT = 7
UNSIGNED_INT = 8
LONG = 9
UNSIGNED_LONG = 10
LONG_LONG = 11
UNSIGNED_LONG_LONG = 12
SSIZE_T = 13
SIZE_T = 14
HALF_PRECISION_FLOAT = 15
FLOAT = 16
DOUBLE = 17
CHAR_ARRAY = 18
VOID_POINTER = 19
FIELD_TYPE = {
'x': FieldType.PAD,
'c': FieldType.CHAR,
'b': FieldType.SIGNED_CHAR,
'B': FieldType.UNSIGNED_CHAR,
'?': FieldType.BOOL,
'h': FieldType.SHORT,
'H': FieldType.UNSIGNED_SHORT,
'i': FieldType.INT,
'I': FieldType.UNSIGNED_INT,
'l': FieldType.LONG,
'L': FieldType.UNSIGNED_LONG,
'q': FieldType.LONG_LONG,
'Q': FieldType.UNSIGNED_LONG_LONG,
'n': FieldType.SSIZE_T,
'N': FieldType.SIZE_T,
'e': FieldType.HALF_PRECISION_FLOAT,
'f': FieldType.FLOAT,
'd': FieldType.DOUBLE,
's': FieldType.CHAR_ARRAY,
'p': FieldType.CHAR_ARRAY,
'P': FieldType.VOID_POINTER,
}
FORMAT_STRING = {}
for k, v in FIELD_TYPE.items():
FORMAT_STRING[v] = k
FORMAT_STRING[FieldType.CHAR_ARRAY] = 's'
[docs]class FieldList:
"""Simply a list of fields, along with a byte_order.
"""
def __init__(self, fields: List[FieldType] = [],
byte_order: ByteOrder = ByteOrder.NATIVE):
self.fields = fields # type: List[FieldType]
self.byte_order = byte_order # type: ByteOrder
[docs] @classmethod
def from_string(cls, fmt_string: str) -> 'FieldList':
"""Construct a :py:class:`FieldList` from a format string.
Args:
fmt_string: Format string conformant to the specification in
:py:mod:`struct`.
Returns:
:py:class:`FieldList` object representing the parsed string as a
flat AST.
"""
if len(fmt_string) == 0:
return FieldList()
start = 0
byte_order = ByteOrder.NATIVE
if fmt_string[0] in BYTE_ORDER:
start = 1
byte_order = BYTE_ORDER[fmt_string[0]]
if len(fmt_string) == start:
return FieldList()
return FieldList([FIELD_TYPE[c] for c in fmt_string[start:]],
byte_order)
[docs] def to_string(self) -> str:
"""Convert the list of fields back into a format string.
Returns:
Format string.
"""
if len(self.fields) == 0:
return ''
# TODO distinguish between "native" orderings
return ORDER_STRING[self.byte_order] + ''.join(map(
lambda field: FORMAT_STRING[field],
self.fields
))
def __len__(self):
return len(self.fields)
def __getitem__(self, item: int) -> FieldType:
return self.fields[item]
def __contains__(self, item: int):
return item in self.fields
[docs]def parse(format_str: str) -> FieldList:
"""Helper to parse a format string into a :py:class:`FieldList`.
Args:
format_str: Format string, as specified by :py:mod:`struct`.
Returns:
:py:class:`FieldList` object representing the parsed format string.
"""
return FieldList.from_string(format_str)