sltcodec 0.4.0__tar.gz → 0.5.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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: sltcodec
3
- Version: 0.4.0
3
+ Version: 0.5.0
4
4
  Summary: Decode and encode bytearrays according to struct layout definitions using sltcore.
5
5
  Project-URL: Homepage, https://github.com/fangface-hub/StructLayoutToolkitCodec
6
6
  Project-URL: Documentation, https://readthedocs.org
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "sltcodec"
3
- version = "0.4.0"
3
+ version = "0.5.0"
4
4
  description = "Decode and encode bytearrays according to struct layout definitions using sltcore."
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.12"
@@ -319,34 +319,45 @@ def decode_field(
319
319
  return None
320
320
  info = bits_get(data, offset, size, scale=field_def.scale)
321
321
  resolved_type = _resolve_field_type(field_def.type, struct_def_dict, env)
322
+ resolved_field_def = FieldDef(
323
+ name=field_def.name,
324
+ offset=offset,
325
+ size=size,
326
+ type=resolved_type,
327
+ scale=field_def.scale,
328
+ repeat=field_def.repeat,
329
+ description=field_def.description,
330
+ range_expression=field_def.range_expression,
331
+ enum_def=field_def.enum_def,
332
+ )
322
333
  if isinstance(resolved_type, StructDef):
323
334
  return FieldInstance(
324
- field_def=field_def,
335
+ field_def=resolved_field_def,
325
336
  value=decode(resolved_type, bytearray(info.to_bytes),
326
337
  struct_def_dict, enum_def_dict,
327
338
  padding_alignment_bits),
328
339
  )
329
340
  if resolved_type == "bool":
330
- return FieldInstance.from_value(field_def,
341
+ return FieldInstance.from_value(resolved_field_def,
331
342
  info.to_bool,
332
343
  enum_def_dict=enum_def_dict)
333
344
  if resolved_type == "signed int":
334
- return FieldInstance.from_value(field_def,
345
+ return FieldInstance.from_value(resolved_field_def,
335
346
  info.to_signed_int,
336
347
  enum_def_dict=enum_def_dict)
337
348
  if resolved_type in ["int", "unsigned int"]:
338
- return FieldInstance.from_value(field_def,
349
+ return FieldInstance.from_value(resolved_field_def,
339
350
  info.to_unsigned_int,
340
351
  enum_def_dict=enum_def_dict)
341
352
  if resolved_type == "float":
342
- return FieldInstance.from_value(field_def,
353
+ return FieldInstance.from_value(resolved_field_def,
343
354
  info.to_float,
344
355
  enum_def_dict=enum_def_dict)
345
356
  if resolved_type in ["bytearray", "bytes"]:
346
- return FieldInstance.from_value(field_def,
357
+ return FieldInstance.from_value(resolved_field_def,
347
358
  info.to_bytes,
348
359
  enum_def_dict=enum_def_dict)
349
- return FieldInstance.from_value(field_def,
360
+ return FieldInstance.from_value(resolved_field_def,
350
361
  info.raw_value,
351
362
  enum_def_dict=enum_def_dict)
352
363
 
@@ -4,12 +4,14 @@ from __future__ import annotations
4
4
  import json
5
5
  import re
6
6
  from dataclasses import dataclass, field
7
+ from functools import total_ordering
7
8
  from typing import Any
8
9
 
9
10
  from sltcalc import SltEval
10
11
  from sltcore import Info, InfoSize
11
12
 
12
13
 
14
+ @total_ordering
13
15
  @dataclass(frozen=True)
14
16
  class EnumDef:
15
17
  """An enumeration definition."""
@@ -67,7 +69,22 @@ class EnumDef:
67
69
  """Create an enum definition from a JSON string."""
68
70
  return cls.from_dict(json.loads(data))
69
71
 
72
+ def __lt__(self, other: object) -> bool:
73
+ """Compare enum definitions using a stable serialized sort key."""
74
+ if not isinstance(other, EnumDef):
75
+ return NotImplemented
76
+ return self._sort_key() < other._sort_key()
77
+
78
+ def _sort_key(self) -> tuple[Any, ...]:
79
+ """Build a stable comparison key for ordering enum definitions."""
80
+ return (
81
+ self.name,
82
+ "" if self.description is None else self.description,
83
+ json.dumps(self.values, sort_keys=True),
84
+ )
70
85
 
86
+
87
+ @total_ordering
71
88
  @dataclass(frozen=True)
72
89
  class FieldDef:
73
90
  """A field in a structured layout."""
@@ -283,6 +300,7 @@ class FieldDef:
283
300
  sort_keys=True)
284
301
 
285
302
 
303
+ @total_ordering
286
304
  @dataclass(frozen=True)
287
305
  class FieldInstance:
288
306
  """A decoded/encodable field value with its field definition."""
@@ -355,6 +373,7 @@ class FieldInstance:
355
373
  return None
356
374
 
357
375
 
376
+ @total_ordering
358
377
  @dataclass(frozen=True)
359
378
  class StructDef:
360
379
  """A structured layout definition that groups multiple fields."""
@@ -369,6 +388,21 @@ class StructDef:
369
388
  metadata={"desc": "The fields of the structure"},
370
389
  )
371
390
 
391
+ def __lt__(self, other: object) -> bool:
392
+ """Compare structure definitions using a stable serialized sort key."""
393
+ if not isinstance(other, StructDef):
394
+ return NotImplemented
395
+ return self._sort_key() < other._sort_key()
396
+
397
+ def _sort_key(self) -> tuple[Any, ...]:
398
+ """Build a stable comparison key for ordering structure definitions."""
399
+ return (
400
+ self.name,
401
+ "" if self.description is None else self.description,
402
+ json.dumps([field.to_dict() for field in self.fields],
403
+ sort_keys=True),
404
+ )
405
+
372
406
  def to_dict(self) -> dict[str, Any]:
373
407
  """Convert this structure definition to JSON-serializable data."""
374
408
  return {
@@ -402,6 +436,7 @@ class StructDef:
402
436
  return cls.from_dict(json.loads(data))
403
437
 
404
438
 
439
+ @total_ordering
405
440
  @dataclass
406
441
  class StructInstance:
407
442
  """A decoded/encodable structure instance."""
@@ -454,6 +489,23 @@ class StructInstance:
454
489
  """Return one field instance by index."""
455
490
  return self.field_instances[index]
456
491
 
492
+ def __lt__(self, other: object) -> bool:
493
+ """Compare structure instances using a stable serialized sort key."""
494
+ if not isinstance(other, StructInstance):
495
+ return NotImplemented
496
+ return self._sort_key() < other._sort_key()
497
+
498
+ def _sort_key(self) -> tuple[Any, ...]:
499
+ """Build a stable comparison key for ordering structure instances."""
500
+ return (
501
+ self.struct_def._sort_key(),
502
+ json.dumps([
503
+ field_instance.to_dict()
504
+ for field_instance in self.field_instances
505
+ ],
506
+ sort_keys=True),
507
+ )
508
+
457
509
  def _sort_field_instances(self) -> None:
458
510
  """Keep field instances sorted by FieldDef order."""
459
511
  self.field_instances.sort()
@@ -466,14 +518,25 @@ class StructInstance:
466
518
  ]
467
519
  non_padding_instances.sort()
468
520
 
521
+ has_unresolved_layout = any(
522
+ not isinstance(field_instance.field_def.offset, InfoSize)
523
+ or not isinstance(field_instance.field_def.size, InfoSize)
524
+ for field_instance in non_padding_instances)
525
+ if has_unresolved_layout:
526
+ # Layout expressions are resolved later in codec paths.
527
+ self.field_instances = non_padding_instances
528
+ self._sort_field_instances()
529
+ self._update_size()
530
+ return
531
+
469
532
  rebuilt_instances: list[FieldInstance] = []
470
533
  current_offset = InfoSize(0, 0)
471
534
  padding_index = 0
472
535
 
473
536
  for field_instance in non_padding_instances:
474
537
  field_def = field_instance.field_def
475
- field_offset = self._resolve_field_offset(field_def)
476
- field_size = self._resolve_field_size(field_def)
538
+ field_offset = field_def.offset
539
+ field_size = field_def.size
477
540
  if field_offset > current_offset:
478
541
  gap_size = field_offset - current_offset
479
542
  if gap_size.byte > 0 or gap_size.bit > 0:
@@ -521,25 +584,15 @@ class StructInstance:
521
584
  max_end_offset = InfoSize(0, 0)
522
585
  for field_instance in self.field_instances:
523
586
  field_def = field_instance.field_def
524
- field_offset = self._resolve_field_offset(field_def)
525
- field_size = self._resolve_field_size(field_def)
587
+ if not isinstance(field_def.offset, InfoSize):
588
+ continue
589
+ if not isinstance(field_def.size, InfoSize):
590
+ continue
591
+ field_offset = field_def.offset
592
+ field_size = field_def.size
526
593
  field_end = field_offset + field_size
527
594
  if field_end > max_end_offset:
528
595
  max_end_offset = field_end
529
596
 
530
597
  if max_end_offset > self.size:
531
598
  self.size = max_end_offset
532
-
533
- @staticmethod
534
- def _resolve_field_offset(field_def: FieldDef) -> InfoSize:
535
- """Resolve field offsets to InfoSize values when possible."""
536
- if isinstance(field_def.offset, InfoSize):
537
- return field_def.offset
538
- return InfoSize(0, 0)
539
-
540
- @staticmethod
541
- def _resolve_field_size(field_def: FieldDef) -> InfoSize:
542
- """Resolve field sizes to InfoSize values when possible."""
543
- if isinstance(field_def.size, InfoSize):
544
- return field_def.size
545
- return InfoSize(0, 0)
@@ -475,10 +475,11 @@ def test_type_expression_uses_previous_field_value():
475
475
  bytearray(),
476
476
  )
477
477
  decoded_int = decode(struct_def, encoded_int)
478
- assert decoded_int.field_instances == [
479
- FieldInstance(struct_def[0], 1),
480
- FieldInstance(struct_def[1], 7),
481
- ]
478
+ assert decoded_int.field_instances[0] == FieldInstance(struct_def[0], 1)
479
+ assert decoded_int.field_instances[1].field_def.offset == InfoSize(1, 0)
480
+ assert decoded_int.field_instances[1].field_def.size == InfoSize(1, 0)
481
+ assert decoded_int.field_instances[1].field_def.type == "int"
482
+ assert decoded_int.field_instances[1].value == 7
482
483
 
483
484
  encoded_float = encode(
484
485
  StructInstance(struct_def=StructDef(fields=struct_def),
@@ -490,7 +491,9 @@ def test_type_expression_uses_previous_field_value():
490
491
  )
491
492
  decoded_float = decode(struct_def, encoded_float)
492
493
  assert decoded_float.field_instances[0] == FieldInstance(struct_def[0], 2)
493
- assert decoded_float.field_instances[1].field_def == struct_def[1]
494
+ assert decoded_float.field_instances[1].field_def.offset == InfoSize(1, 0)
495
+ assert decoded_float.field_instances[1].field_def.size == InfoSize(4, 0)
496
+ assert decoded_float.field_instances[1].field_def.type == "float"
494
497
  assert decoded_float.field_instances[1].value == 1.5
495
498
 
496
499
 
@@ -74,7 +74,7 @@ wheels = [
74
74
 
75
75
  [[package]]
76
76
  name = "sltcodec"
77
- version = "0.3.0"
77
+ version = "0.4.0"
78
78
  source = { editable = "." }
79
79
  dependencies = [
80
80
  { name = "sltcalc" },
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes