sltcodec 2.4.0__tar.gz → 2.4.1__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: 2.4.0
3
+ Version: 2.4.1
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 = "2.4.0"
3
+ version = "2.4.1"
4
4
  description = "Decode and encode bytearrays according to struct layout definitions using sltcore."
5
5
  readme = "README_pypi.md"
6
6
  requires-python = ">=3.12"
@@ -134,8 +134,20 @@ def load_enum_def_dict(path: str | Path) -> dict[str, EnumDef]:
134
134
  def _resolve_info_size(value: InfoSize | str, env: dict[str, Any]) -> InfoSize:
135
135
  """Resolve an InfoSize value that can be static or expression-based."""
136
136
  if isinstance(value, str):
137
- stleval = SltEval(env)
138
- resolved_byte = stleval.eval(value)
137
+ eval_env = dict(env)
138
+
139
+ def replace_info_size(match: re.Match[str]) -> str:
140
+ name = f"_info_size_{len(eval_env)}"
141
+ eval_env[name] = InfoSize(int(match.group(1)), int(match.group(2)))
142
+ return name
143
+
144
+ expression = re.sub(
145
+ r"\bInfoSize\s*\(\s*(-?\d+)\s*,\s*(-?\d+)\s*\)",
146
+ replace_info_size,
147
+ value,
148
+ )
149
+ stleval = SltEval(eval_env)
150
+ resolved_byte = stleval.eval(expression)
139
151
  if isinstance(resolved_byte, InfoSize):
140
152
  return resolved_byte
141
153
  return InfoSize(resolved_byte, 0)
@@ -164,6 +176,13 @@ def _is_padding_field_def(field_def: FieldDef) -> bool:
164
176
  and field_def.type in ["bytes", "bytearray"])
165
177
 
166
178
 
179
+ def _struct_def_has_dynamic_extent(struct_def: StructDef) -> bool:
180
+ """Check whether a nested structure can consume data beyond its fields."""
181
+ return any(field_def.repeat == "end" or isinstance(field_def.offset, str)
182
+ or isinstance(field_def.size, str)
183
+ for field_def in struct_def.fields)
184
+
185
+
167
186
  def _validate_padding_alignment_bits(padding_alignment_bits: int) -> None:
168
187
  """Validate padding alignment as a positive power-of-two bit size."""
169
188
  if (isinstance(padding_alignment_bits, bool)
@@ -415,7 +434,10 @@ def decode_field(
415
434
  nested_layout = _layout_for_struct_def(resolved_type,
416
435
  type_dict,
417
436
  name=field_def.name)
418
- nested_value = decode(nested_layout, bytearray(info.to_bytes),
437
+ nested_data = bytearray(info.to_bytes)
438
+ if _struct_def_has_dynamic_extent(resolved_type) and offset.bit == 0:
439
+ nested_data = bytearray(data[offset.byte:])
440
+ nested_value = decode(nested_layout, nested_data,
419
441
  padding_alignment_bits)
420
442
  actual_size = nested_value.size
421
443
  else:
@@ -232,6 +232,38 @@ def test_repeat_end_stops_when_offset_is_out_of_range():
232
232
  assert [instance.value for instance in decoded.field_instances] == [None]
233
233
 
234
234
 
235
+ def test_info_size_expression_resolves_constructor():
236
+ """Test that serialized InfoSize constructors work in expressions."""
237
+ resolved = _resolve_info_size("InfoSize(1, 0) * count", {"count": 3})
238
+
239
+ assert resolved == InfoSize(3, 0)
240
+
241
+
242
+ def test_repeat_end_uses_dynamic_nested_struct_size():
243
+ """Test repeat='end' advances by a variable-size nested structure."""
244
+ length_field = FieldDef(name="length",
245
+ offset=InfoSize(0, 0),
246
+ size=InfoSize(1, 0),
247
+ type="unsigned int")
248
+ payload_field = FieldDef(name="payload",
249
+ offset=InfoSize(1, 0),
250
+ size="InfoSize(1, 0) * length",
251
+ type="bytearray")
252
+ record_field = FieldDef(
253
+ name="record",
254
+ offset=InfoSize(0, 0),
255
+ size=InfoSize(1, 0),
256
+ type=StructDef(fields=[length_field, payload_field]),
257
+ repeat="end")
258
+
259
+ decoded = decode(layout_for([record_field]), bytearray(b"\x02ab\x01c"))
260
+
261
+ assert [instance.field_def.name for instance in decoded.field_instances
262
+ ] == ["record[0]", "record[1]"]
263
+ assert [instance.value.size for instance in decoded.field_instances
264
+ ] == [InfoSize(3, 0), InfoSize(2, 0)]
265
+
266
+
235
267
  def test_decode_out_of_range_sets_current_and_tail_values_to_none():
236
268
  """Test out-of-range decode fills current/tail field values with None."""
237
269
  fields = [
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
File without changes
File without changes
File without changes
File without changes