c-struct-data-parser 0.1.0__tar.gz

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.
@@ -0,0 +1,89 @@
1
+ Metadata-Version: 2.3
2
+ Name: c-struct-data-parser
3
+ Version: 0.1.0
4
+ Summary: Declaratively define and parse C data structures
5
+ Author: Gregory.Kuhn
6
+ Author-email: Gregory.Kuhn <gregory.kuhn@analog.com>
7
+ Requires-Python: >=3.11
8
+ Description-Content-Type: text/markdown
9
+
10
+ # c_struct_data_parser
11
+
12
+ Intended to facilitate declarative, dynamic definition of C data structures.
13
+ The library will autogenerate a parser which you can connect to a `BytesReader` or some IO based variant.
14
+
15
+ You're better off looking at https://github.com/fox-it/dissect.cstruct
16
+
17
+ Examples from `test_basic.py`:
18
+
19
+ ```python
20
+ Struct2Int = create_struct_definition(
21
+ "Struct2Int",
22
+ {
23
+ "value_a": Int4Definition,
24
+ "value_b": Int4Definition,
25
+ },
26
+ )
27
+
28
+
29
+ def test_struct2Int() -> None:
30
+ value_a = 0x12345678
31
+ value_b = 0x99887766
32
+ bs = b"".join(map(int_to_le_bytes_4, [value_a, value_b]))
33
+ bytes_reader = BytesReader(address=0, bs=bs)
34
+ struct_2_int, new_reader = Struct2Int.parser(bytes_reader)
35
+ assert struct_2_int.value_a.value == value_a
36
+ assert struct_2_int.value_b.value == value_b
37
+
38
+ print(struct_2_int)
39
+
40
+
41
+ BitFieldsExample = create_bit_fields_definition(
42
+ "BitFieldsExample",
43
+ Int4Definition,
44
+ {
45
+ "field_a": 4,
46
+ "reserved_1": 5,
47
+ "field_b": 5,
48
+ },
49
+ )
50
+
51
+
52
+ def test_bit_fields() -> None:
53
+ val = 0x123456
54
+ bs = int_to_le_bytes_4(val)
55
+ bytes_reader = BytesReader(address=0, bs=bs)
56
+ bit_fields_example, new_reader = BitFieldsExample.parser(bytes_reader)
57
+ assert bit_fields_example.field_a.value == val & 0xF
58
+ assert bit_fields_example.field_b.value == (val >> 9) & 0x1F
59
+ print(bit_fields_example)
60
+
61
+
62
+ Struct2IntPointer = create_pointer_definition(
63
+ Struct2Int,
64
+ Int4Definition,
65
+ )
66
+
67
+
68
+ def test_pointer_type() -> None:
69
+ value_a = 0x12345678
70
+ value_b = 0x99887766
71
+ bs_struct = b"".join(map(int_to_le_bytes_4, [value_a, value_b]))
72
+ bs_pointer = int_to_le_bytes_4(0x2000)
73
+ bs = b"".join([bs_struct, bs_pointer])
74
+ bytes_reader = BytesReader(address=0x2000, bs=bs)
75
+
76
+ struct_2_int, new_reader = Struct2Int.parser(bytes_reader)
77
+ assert struct_2_int.value_a.value == value_a
78
+ assert struct_2_int.value_b.value == value_b
79
+
80
+ p_struct_2_int, new_reader_2 = Struct2IntPointer.parser(new_reader)
81
+
82
+ print(p_struct_2_int)
83
+
84
+ struct_2_int_copy = p_struct_2_int.resolve(new_reader_2)
85
+
86
+ assert struct_2_int_copy.value_a.value == value_a
87
+ assert struct_2_int_copy.value_b.value == value_b
88
+
89
+ ```
@@ -0,0 +1,80 @@
1
+ # c_struct_data_parser
2
+
3
+ Intended to facilitate declarative, dynamic definition of C data structures.
4
+ The library will autogenerate a parser which you can connect to a `BytesReader` or some IO based variant.
5
+
6
+ You're better off looking at https://github.com/fox-it/dissect.cstruct
7
+
8
+ Examples from `test_basic.py`:
9
+
10
+ ```python
11
+ Struct2Int = create_struct_definition(
12
+ "Struct2Int",
13
+ {
14
+ "value_a": Int4Definition,
15
+ "value_b": Int4Definition,
16
+ },
17
+ )
18
+
19
+
20
+ def test_struct2Int() -> None:
21
+ value_a = 0x12345678
22
+ value_b = 0x99887766
23
+ bs = b"".join(map(int_to_le_bytes_4, [value_a, value_b]))
24
+ bytes_reader = BytesReader(address=0, bs=bs)
25
+ struct_2_int, new_reader = Struct2Int.parser(bytes_reader)
26
+ assert struct_2_int.value_a.value == value_a
27
+ assert struct_2_int.value_b.value == value_b
28
+
29
+ print(struct_2_int)
30
+
31
+
32
+ BitFieldsExample = create_bit_fields_definition(
33
+ "BitFieldsExample",
34
+ Int4Definition,
35
+ {
36
+ "field_a": 4,
37
+ "reserved_1": 5,
38
+ "field_b": 5,
39
+ },
40
+ )
41
+
42
+
43
+ def test_bit_fields() -> None:
44
+ val = 0x123456
45
+ bs = int_to_le_bytes_4(val)
46
+ bytes_reader = BytesReader(address=0, bs=bs)
47
+ bit_fields_example, new_reader = BitFieldsExample.parser(bytes_reader)
48
+ assert bit_fields_example.field_a.value == val & 0xF
49
+ assert bit_fields_example.field_b.value == (val >> 9) & 0x1F
50
+ print(bit_fields_example)
51
+
52
+
53
+ Struct2IntPointer = create_pointer_definition(
54
+ Struct2Int,
55
+ Int4Definition,
56
+ )
57
+
58
+
59
+ def test_pointer_type() -> None:
60
+ value_a = 0x12345678
61
+ value_b = 0x99887766
62
+ bs_struct = b"".join(map(int_to_le_bytes_4, [value_a, value_b]))
63
+ bs_pointer = int_to_le_bytes_4(0x2000)
64
+ bs = b"".join([bs_struct, bs_pointer])
65
+ bytes_reader = BytesReader(address=0x2000, bs=bs)
66
+
67
+ struct_2_int, new_reader = Struct2Int.parser(bytes_reader)
68
+ assert struct_2_int.value_a.value == value_a
69
+ assert struct_2_int.value_b.value == value_b
70
+
71
+ p_struct_2_int, new_reader_2 = Struct2IntPointer.parser(new_reader)
72
+
73
+ print(p_struct_2_int)
74
+
75
+ struct_2_int_copy = p_struct_2_int.resolve(new_reader_2)
76
+
77
+ assert struct_2_int_copy.value_a.value == value_a
78
+ assert struct_2_int_copy.value_b.value == value_b
79
+
80
+ ```
@@ -0,0 +1,20 @@
1
+ [project]
2
+ name = "c-struct-data-parser"
3
+ version = "0.1.0"
4
+ description = "Declaratively define and parse C data structures"
5
+ readme = "README.md"
6
+ authors = [
7
+ { name = "Gregory.Kuhn", email = "gregory.kuhn@analog.com" }
8
+ ]
9
+ requires-python = ">=3.11"
10
+ dependencies = []
11
+
12
+ [build-system]
13
+ requires = ["uv_build>=0.9.8,<0.10.0"]
14
+ build-backend = "uv_build"
15
+
16
+ [dependency-groups]
17
+ dev = [
18
+ "pre-commit>=4.5.1",
19
+ "pytest>=9.0.2",
20
+ ]
@@ -0,0 +1,23 @@
1
+ from .reader_abc import AddressData, Reader
2
+
3
+
4
+ class BytesReader(Reader):
5
+ def __init__(self, address: int, bs: bytes):
6
+ super().__init__(address)
7
+ self.bs = bs
8
+
9
+ def read(self, size: int) -> AddressData:
10
+ current = self.offset
11
+ self.offset += size
12
+ read_bytes = self.bs[current : current + size]
13
+ if len(read_bytes) < size:
14
+ raise ValueError(f"Failed to read requested number of bytes: {size}")
15
+ return self.address + current, read_bytes
16
+
17
+ def new_reader(self, address: int) -> Reader:
18
+ if address < self.address:
19
+ raise ValueError(
20
+ f"Supplied address: {address:#010x} cannot be lower than original address: {self.address:#010x}"
21
+ )
22
+ offset = address - self.address
23
+ return type(self)(address, self.bs[offset:])
@@ -0,0 +1,440 @@
1
+ from __future__ import annotations
2
+
3
+ from abc import ABC, abstractmethod
4
+ from dataclasses import dataclass
5
+ from enum import Enum
6
+ from functools import partial, reduce
7
+ from itertools import chain
8
+ from operator import attrgetter, methodcaller
9
+ from textwrap import indent
10
+ from typing import Dict, Literal, Optional, Sequence, Tuple, Type
11
+
12
+ from .parser import create_repeat_parser, create_sequence_parser, Parser, Reader
13
+
14
+
15
+ int_from_bytes_le = partial(int.from_bytes, byteorder="little")
16
+
17
+ get_parser = attrgetter("parser")
18
+ from_register = partial(methodcaller, "from_register")
19
+ get_size = attrgetter("size")
20
+
21
+ indent_4 = partial(indent, prefix=" ")
22
+
23
+
24
+ @dataclass
25
+ class Metadata:
26
+ address: int
27
+
28
+
29
+ class DataDefinition(ABC):
30
+ size: int = 0
31
+
32
+ @classmethod
33
+ @abstractmethod
34
+ def parser(cls, reader: Reader) -> Tuple[DataDefinition, Reader]: ...
35
+
36
+ def __init__(
37
+ self,
38
+ metadata: Optional[Metadata],
39
+ ):
40
+ self.metadata = metadata
41
+
42
+ def __repr__(self) -> str:
43
+ cls_name = type(self).__name__
44
+ return f"{cls_name}({self.add_metadata_repr()})"
45
+
46
+ def add_metadata_repr(self, *args: str) -> str:
47
+ m = self.metadata
48
+ args_str = ", ".join(args)
49
+ if m:
50
+ prefix = ", " if args else ""
51
+ return f"{args_str}{prefix}Metadata(address={m.address:#x})"
52
+ return args_str
53
+
54
+
55
+ class IntDefinition(DataDefinition):
56
+ size: int = 0 # num bytes
57
+ byteorder: Literal["little", "big"] = "little"
58
+ _enum_type: Optional[Type[Enum]] = None
59
+
60
+ def __init__(
61
+ self,
62
+ value: int | Enum,
63
+ metadata: Optional[Metadata] = None,
64
+ ):
65
+
66
+ super().__init__(metadata)
67
+ if isinstance(value, int):
68
+ self.value = value
69
+ elif self._enum_type and isinstance(value, self._enum_type):
70
+ self.value = value.value
71
+ else:
72
+ raise ValueError(f"Unexpected value type: {type(value)}")
73
+
74
+ @classmethod
75
+ def parser(cls, reader: Reader) -> Tuple[IntDefinition, Reader]:
76
+ address, data_bytes = reader.read(cls.size)
77
+ return cls(int.from_bytes(data_bytes, byteorder=cls.byteorder), metadata=Metadata(address)), reader
78
+
79
+ def __repr__(self) -> str:
80
+ cls_name = type(self).__name__
81
+ val = self.value
82
+ enum_type = self._enum_type
83
+ if enum_type:
84
+ try:
85
+ val_str = str(enum_type(val))
86
+ except ValueError:
87
+ val_str = hex(val)
88
+ else:
89
+ val_str = hex(val)
90
+
91
+ return f"{cls_name}({self.add_metadata_repr(val_str)})"
92
+
93
+ def __str__(self) -> str:
94
+ cls_name = type(self).__name__
95
+ val = self.value
96
+ enum_type = self._enum_type
97
+ if enum_type:
98
+ try:
99
+ val_str = repr(enum_type(val)) # repr(Enum(<num>)) looks more like str(Enum(<num>)) and vice versa
100
+ except ValueError:
101
+ val_str = f"{val:#x} ({val}) not a valid Enumeration of type: {enum_type}"
102
+ else:
103
+ val_str = hex(val)
104
+
105
+ return f"{cls_name}({self.add_metadata_repr(val_str)})"
106
+
107
+
108
+ class Int4Definition(IntDefinition):
109
+ size: int = 4
110
+
111
+
112
+ class Int1Definition(IntDefinition):
113
+ size: int = 1
114
+
115
+
116
+ class Int2Definition(IntDefinition):
117
+ size: int = 2
118
+
119
+
120
+ def create_int_definition(
121
+ name: str,
122
+ int_definition: Type[IntDefinition],
123
+ enumeration: Optional[Type[Enum]] = None,
124
+ ) -> Type[IntDefinition]:
125
+ attrs = {"_enum_type": enumeration} if enumeration else {}
126
+ return type(
127
+ name,
128
+ (int_definition,),
129
+ attrs,
130
+ )
131
+
132
+
133
+ class StructDefinition(DataDefinition):
134
+ size: int = 0
135
+ field_types: Dict[str, Type[DataDefinition]] = {}
136
+
137
+ def __init__(
138
+ self,
139
+ fields: Sequence[DataDefinition],
140
+ metadata: Optional[Metadata] = None,
141
+ ):
142
+ super().__init__(metadata)
143
+ self.fields = tuple(fields)
144
+ fields_len = len(fields)
145
+ field_types_len = len(self.field_types)
146
+ if fields_len != field_types_len:
147
+ raise ValueError(f"Supplied number of fields: {fields_len} doesn't match expectation: {field_types_len}")
148
+ for (k, t), v in zip(self.field_types.items(), fields):
149
+ if not isinstance(v, t):
150
+ raise ValueError(f"Got unexpected value: {v} for type: {t}")
151
+ setattr(self, k, v)
152
+
153
+ def __str__(self) -> str:
154
+ cls_name = type(self).__name__
155
+
156
+ fields_strs = map(lambda k, v: f"{k}: {v}", self.field_types.keys(), map(str, self.fields))
157
+ fields_strs_indented = map(indent_4, fields_strs)
158
+ fields_str = "\n".join(fields_strs_indented)
159
+ return f"{cls_name} {self.add_metadata_repr()}:\n{fields_str}"
160
+
161
+ @classmethod
162
+ def parser(cls, reader: Reader) -> Tuple[StructDefinition, Reader]:
163
+ raise NotImplementedError(f"{cls} is abstract")
164
+
165
+
166
+ @dataclass
167
+ class ForwardPointer:
168
+ address_type: Type[IntDefinition]
169
+
170
+
171
+ def create_forward_pointer(
172
+ address_type: Type[IntDefinition],
173
+ ) -> ForwardPointer:
174
+ return ForwardPointer(address_type)
175
+
176
+
177
+ def replace_forward_pointer(
178
+ target_type: Type[DataDefinition], field_type: Type[DataDefinition] | ForwardPointer
179
+ ) -> Type[DataDefinition]:
180
+ if isinstance(field_type, ForwardPointer):
181
+ return _create_pointer_definition_from_forward_pointer(target_type, field_type)
182
+ else:
183
+ return field_type
184
+
185
+
186
+ def create_struct_definition(
187
+ struct_name: str,
188
+ field_types: Dict[str, Type[DataDefinition] | ForwardPointer],
189
+ ) -> Type[StructDefinition]:
190
+
191
+ new_struct = type(
192
+ struct_name,
193
+ (StructDefinition,),
194
+ {},
195
+ )
196
+
197
+ normalized_field_types = {k: replace_forward_pointer(new_struct, v) for (k, v) in field_types.items()}
198
+ sequence_parser = create_sequence_parser(*map(get_parser, normalized_field_types.values()))
199
+
200
+ @classmethod
201
+ def parser(cls: Type[StructDefinition], reader: Reader) -> Tuple[StructDefinition, Reader]:
202
+ parsed_field_results, new_reader = sequence_parser(reader)
203
+ metadata = Metadata(parsed_field_results[0].metadata.address)
204
+ return cls(parsed_field_results, metadata=metadata), new_reader
205
+
206
+ new_struct.field_types = normalized_field_types
207
+ new_struct.parser = parser
208
+ new_struct.size = sum(map(get_size, normalized_field_types.values()))
209
+
210
+ return new_struct
211
+
212
+
213
+ class BitFieldDefinition:
214
+ size: int
215
+ position: int
216
+ mask: int
217
+
218
+ def __init__(
219
+ self,
220
+ value: int,
221
+ ):
222
+ if value > self.mask:
223
+ raise ValueError(f"Passed in value: {value:#x} greater than mask: {self.mask:#x}")
224
+ self.value = value
225
+
226
+ def __repr__(self):
227
+ cls_name = type(self).__name__
228
+ return f"{cls_name}({self.value:#x})"
229
+
230
+ def __str__(self):
231
+ cls_name = type(self).__name__
232
+ return f"{cls_name}: {self.value:#x}"
233
+
234
+ @classmethod
235
+ def from_register(cls, value: int) -> BitFieldDefinition:
236
+ return cls((value >> cls.position) & cls.mask)
237
+
238
+
239
+ class BitFieldsDefinition(DataDefinition):
240
+ field_types: Dict[str, Type[BitFieldDefinition]]
241
+ IntDefinition: Type[IntDefinition]
242
+ size: int
243
+
244
+ def __init__(
245
+ self,
246
+ value: int,
247
+ metadata: Optional[Metadata],
248
+ ):
249
+ super().__init__(metadata)
250
+ self.value = value
251
+ self.fields = tuple(map(from_register(value), self.field_types.values()))
252
+ fields_len = len(self.fields)
253
+ field_types_len = len(self.field_types)
254
+
255
+ if fields_len != field_types_len:
256
+ raise ValueError(f"Supplied number of fields: {fields_len} doesn't match expectation: {field_types_len}")
257
+ for (k, t), v in zip(self.field_types.items(), self.fields):
258
+ if not isinstance(v, t):
259
+ raise ValueError(f"Got unexpected value: {v} for type: {t}")
260
+ setattr(self, k, v)
261
+
262
+ @classmethod
263
+ def parser(cls, reader: Reader) -> Tuple[BitFieldsDefinition, Reader]:
264
+ int_definition, new_reader = cls.IntDefinition.parser(reader)
265
+ return (
266
+ cls(
267
+ int_definition.value,
268
+ metadata=int_definition.metadata,
269
+ ),
270
+ new_reader,
271
+ )
272
+
273
+ def __repr__(self) -> str:
274
+ cls_name = type(self).__name__
275
+ return f"{cls_name}({self.add_metadata_repr(hex(self.value))})"
276
+
277
+ def __str__(self) -> str:
278
+ cls_name = type(self).__name__
279
+ fields_strs = map(str, self.fields)
280
+ fields_strs_indented = map(indent_4, fields_strs)
281
+ fields_str = "\n".join(fields_strs_indented)
282
+ return f"{cls_name}({self.add_metadata_repr(hex(self.value))})\n{fields_str}"
283
+
284
+
285
+ BitFieldDef = Tuple[str, int]
286
+ BitFieldDefs = Dict[str, int]
287
+
288
+
289
+ def _create_bit_field_definition_from_bit_field_def(position: int, bf_def: BitFieldDef) -> Type[BitFieldDefinition]:
290
+ name, size = bf_def
291
+ bit_field_definition = type(
292
+ name,
293
+ (BitFieldDefinition,),
294
+ {
295
+ "size": size,
296
+ "position": position,
297
+ "mask": (1 << size) - 1,
298
+ },
299
+ )
300
+
301
+ return bit_field_definition
302
+
303
+
304
+ PosFieldsTup = Tuple[int, Tuple[Type[BitFieldDefinition], ...]]
305
+
306
+
307
+ def _create_fields_reducer(pos_fields_tup: PosFieldsTup, field_def: BitFieldDef) -> PosFieldsTup:
308
+ pos, fields = pos_fields_tup
309
+ name, size = field_def
310
+ new_field = _create_bit_field_definition_from_bit_field_def(pos, field_def)
311
+ return (pos + size, tuple(chain(fields, [new_field])))
312
+
313
+
314
+ def create_bit_fields_definition(
315
+ name: str,
316
+ int_definition: Type[IntDefinition],
317
+ field_defs: BitFieldDefs,
318
+ ) -> Type[BitFieldsDefinition]:
319
+ empty_tuple = ()
320
+
321
+ size, fields = reduce(_create_fields_reducer, field_defs.items(), (0, empty_tuple))
322
+ fields_dict = dict(zip(field_defs.keys(), fields, strict=True))
323
+ return type(
324
+ name,
325
+ (BitFieldsDefinition,),
326
+ {
327
+ "field_types": fields_dict,
328
+ "IntDefinition": int_definition,
329
+ "size": int_definition.size,
330
+ },
331
+ )
332
+
333
+
334
+ class PointerDefinition(DataDefinition):
335
+ size: int = 0
336
+ target_type: Type[DataDefinition]
337
+ address_type: Type[IntDefinition] = Int4Definition
338
+ is_forward_ref: bool = False
339
+
340
+ def __init__(
341
+ self,
342
+ address: int,
343
+ metadata: Optional[Metadata],
344
+ ):
345
+ super().__init__(metadata)
346
+ self.address = address
347
+
348
+ def __repr__(self) -> str:
349
+ cls_name = type(self).__name__
350
+ return f"{cls_name}({self.add_metadata_repr(hex(self.address))})"
351
+
352
+ def __str__(self) -> str:
353
+ cls_name = type(self).__name__
354
+
355
+ target_name = self.target_type.__name__
356
+ p_str = f"pointer to object of type: {target_name}"
357
+ return (
358
+ f"{cls_name}({self.add_metadata_repr(hex(self.address))})\n*{self.address:#x} -> {target_name} :: {p_str}"
359
+ )
360
+
361
+ @classmethod
362
+ def parser(cls, reader: Reader) -> Tuple[PointerDefinition, Reader]:
363
+ int_definition, new_reader = cls.address_type.parser(reader)
364
+ return (cls(int_definition.value, metadata=int_definition.metadata), new_reader)
365
+
366
+ def resolve(self, reader: Reader) -> DataDefinition:
367
+ if self.address == 0:
368
+ raise ValueError(f"Attempting to resolve a null pointer: {self}")
369
+ new_reader = reader.new_reader(self.address)
370
+ resolved, _ = self.target_type.parser(new_reader)
371
+ return resolved
372
+
373
+
374
+ def create_pointer_definition(
375
+ target_type: Type[DataDefinition],
376
+ address_type: Type[IntDefinition],
377
+ ) -> Type[PointerDefinition]:
378
+ return type(
379
+ f"Pointer2{target_type.__name__}",
380
+ (PointerDefinition,),
381
+ {
382
+ "target_type": target_type,
383
+ "address_type": address_type,
384
+ "size": target_type.size,
385
+ },
386
+ )
387
+
388
+
389
+ def _create_pointer_definition_from_forward_pointer(
390
+ target_type: Type[DataDefinition],
391
+ forward_pointer: ForwardPointer,
392
+ ) -> Type[PointerDefinition]:
393
+ return create_pointer_definition(
394
+ target_type=target_type,
395
+ address_type=forward_pointer.address_type,
396
+ )
397
+
398
+
399
+ class ArrayDefinition(DataDefinition):
400
+ target_type: Type[DataDefinition]
401
+ size: int
402
+ _fields_parser: Parser
403
+ _array_splitter = "\n" + 80 * "=" + "\n"
404
+
405
+ def __init__(
406
+ self,
407
+ fields: Sequence[DataDefinition],
408
+ metadata: Optional[Metadata],
409
+ ):
410
+ super().__init__(metadata)
411
+ self.fields = fields
412
+
413
+ def __str__(self) -> str:
414
+ cls_name = type(self).__name__
415
+ target_cls = self.target_type.__name__
416
+ fields_strs = map(str, self.fields)
417
+ fields_strs_indented = map(indent_4, fields_strs)
418
+ fields_str = self._array_splitter.join(fields_strs_indented)
419
+ return f"{cls_name}:: {target_cls}[{self.size}] {self.add_metadata_repr()}:\n{fields_str}"
420
+
421
+ @classmethod
422
+ def parser(cls, reader: Reader) -> Tuple[ArrayDefinition, Reader]:
423
+ fields, new_reader = cls._fields_parser(reader)
424
+ return cls(fields, metadata=fields[0].metadata), new_reader
425
+
426
+
427
+ def create_array_definition(
428
+ target_type: Type[DataDefinition],
429
+ size: int,
430
+ ) -> Type[ArrayDefinition]:
431
+ fields_parser = create_repeat_parser(size, target_type.parser)
432
+ return type(
433
+ f"{target_type.__name__}Array",
434
+ (ArrayDefinition,),
435
+ {
436
+ "target_type": target_type,
437
+ "size": size,
438
+ "_fields_parser": fields_parser,
439
+ },
440
+ )
@@ -0,0 +1,78 @@
1
+ from __future__ import annotations
2
+
3
+ from abc import ABC, abstractmethod
4
+ from functools import reduce
5
+ from itertools import chain, repeat
6
+ from typing import (
7
+ Any,
8
+ Callable,
9
+ Tuple,
10
+ TypeVar,
11
+ )
12
+
13
+
14
+ class Reader(ABC):
15
+
16
+ def __init__(self, address: int):
17
+ self.address = address
18
+ self.offset = 0
19
+
20
+ @abstractmethod
21
+ def read(self, size: int) -> bytes:
22
+ self.offset += size
23
+ return bytes(size)
24
+
25
+ def new_reader(self, address: int) -> Reader:
26
+ return type(self)(address)
27
+
28
+
29
+ T = TypeVar("T")
30
+ A = TypeVar("A")
31
+ B = TypeVar("B")
32
+
33
+ Parser = Callable[[Reader], Tuple[T, Reader]]
34
+ MakeParser = Callable[[A], Parser[B]]
35
+ ResultsTup = Tuple[Tuple[Any], Reader]
36
+
37
+
38
+ def fmap_parser(func: Callable[[A], B], parser: Parser[A]) -> Parser[B]:
39
+ def fmapped_parser(reader: Reader) -> Tuple[B, Reader]:
40
+ result_from_parser, result_reader = parser(reader)
41
+ return func(result_from_parser), result_reader
42
+
43
+ return fmapped_parser
44
+
45
+
46
+ def combine_parsers(
47
+ parser_a: Parser[A],
48
+ make_parser_b: MakeParser[A, B],
49
+ ) -> Parser:
50
+ ">>= (bind) if you squint"
51
+
52
+ def combined_parser(reader: Reader) -> Tuple[B, Reader]:
53
+ val_a, reader_a = parser_a(reader)
54
+ parser_b = make_parser_b(val_a)
55
+ return parser_b(reader_a)
56
+
57
+ return combined_parser
58
+
59
+
60
+ def parser_reducer(results_tup: ResultsTup, parser: Parser) -> ResultsTup:
61
+ results, reader = results_tup
62
+ parser_res, parser_reader = parser(reader)
63
+ new_results = tuple(chain(results, [parser_res]))
64
+ return new_results, parser_reader
65
+
66
+
67
+ def create_sequence_parser(*parsers: Parser) -> Parser[Tuple[Any]]:
68
+ empty_tuple = ()
69
+
70
+ def sequence_parser(reader: Reader) -> ResultsTup:
71
+ initial: Tuple[Tuple[Any, ...], Reader] = (empty_tuple, reader)
72
+ return reduce(parser_reducer, parsers, initial)
73
+
74
+ return sequence_parser
75
+
76
+
77
+ def create_repeat_parser(num: int, parser: Parser) -> Parser:
78
+ return create_sequence_parser(*repeat(parser, num))
@@ -0,0 +1,23 @@
1
+ from __future__ import annotations
2
+
3
+ from abc import ABC, abstractmethod
4
+ from typing import Tuple
5
+
6
+
7
+ AddressData = Tuple[int, bytes]
8
+
9
+
10
+ class Reader(ABC):
11
+
12
+ def __init__(self, address: int):
13
+ self.address = address
14
+ self.offset = 0
15
+
16
+ @abstractmethod
17
+ def read(self, size: int) -> AddressData:
18
+ current_offset = self.offset
19
+ self.offset += size
20
+ return (self.address + current_offset, bytes(size))
21
+
22
+ def new_reader(self, address: int) -> Reader:
23
+ return type(self)(address)