sltcodec 1.2.0__tar.gz → 1.3.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: 1.2.0
3
+ Version: 1.3.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
@@ -145,7 +145,7 @@ Output:
145
145
  ```python
146
146
  bytearray(b"\x03\x04")
147
147
  StructInstance(struct_def=StructDef(...), field_instances=[...])
148
- [FieldInstance(field_def=FieldDef(name='pair', offset=InfoSize(byte=0, bit=0), size=InfoSize(byte=2, bit=0), type=[...], scale=1.0, repeat=None),
148
+ [FieldInstance(field_def=FieldDef(name='pair', offset=InfoSize(byte=0, bit=0), size=InfoSize(byte=2, bit=0), type=StructDef(...), scale=1.0, repeat=None),
149
149
  value=StructInstance(struct_def=StructDef(...), field_instances=[...]))]
150
150
  ```
151
151
 
@@ -199,66 +199,87 @@ print(encoded_int, decoded_int)
199
199
  print(encoded_float, decoded_float)
200
200
  ```
201
201
 
202
- ## Saving And Loading StructDef Dictionaries
202
+ ## Saving And Loading Type Dictionaries
203
203
 
204
- You can persist reusable structure definitions by name with
205
- `save_struct_def_dict` / `load_struct_def_dict`.
204
+ You can persist reusable structure and enum definitions together via
205
+ `TypeDict` with `save_type_dict` / `load_type_dict`.
206
+
207
+ `InfoSize` values in `offset` and `size` are saved as typed dictionaries in
208
+ JSON. Expression-based offsets and sizes remain strings and are resolved when
209
+ the definition is used.
206
210
 
207
211
  ```python
208
212
  from pathlib import Path
209
213
 
210
214
  from sltcore import InfoSize
211
- from sltcodec import FieldDef, StructDef, load_struct_def_dict, save_struct_def_dict
212
-
213
- struct_defs = {
214
- "Header": StructDef(
215
- name="Header",
216
- description="Simple header",
217
- fields=[
218
- FieldDef(name="kind", offset=InfoSize(0, 0), size=InfoSize(1, 0), type="unsigned int"),
219
- FieldDef(name="flags", offset=InfoSize(1, 0), size=InfoSize(1, 0), type="unsigned int"),
220
- ],
221
- )
222
- }
215
+ from sltcodec import EnumDef, FieldDef, StructDef, TypeDict, load_type_dict, save_type_dict
216
+
217
+ type_dict = TypeDict(
218
+ struct_dict={
219
+ "Header": StructDef(
220
+ name="Header",
221
+ description="Simple header",
222
+ fields=[
223
+ FieldDef(name="kind", offset=InfoSize(0, 0), size=InfoSize(1, 0), type="unsigned int"),
224
+ FieldDef(name="flags", offset=InfoSize(1, 0), size=InfoSize(1, 0), type="unsigned int"),
225
+ ],
226
+ )
227
+ },
228
+ enum_dict={
229
+ "Status": EnumDef(name="Status", values={"OK": 0, "NG": 1}),
230
+ }
231
+ )
223
232
 
224
233
  path = Path("struct_defs.json")
225
- save_struct_def_dict(path, struct_defs)
226
- loaded = load_struct_def_dict(path)
234
+ save_type_dict(path, type_dict)
235
+ loaded = load_type_dict(path)
227
236
 
228
- print(loaded["Header"].name)
229
- print(loaded["Header"].description)
237
+ print(loaded.struct_dict["Header"].name)
238
+ print(loaded.struct_dict["Header"].description)
239
+ print(loaded.enum_dict["Status"].values["NG"])
230
240
  ```
231
241
 
232
242
  Example output:
233
243
 
234
244
  ```python
235
- bytearray(b"\x01\x07") StructInstance(struct_def=StructDef(...), field_instances=[...])
236
- bytearray(b"\x02?\xc0\x00\x00") StructInstance(struct_def=StructDef(...), field_instances=[...])
245
+ Header
246
+ Simple header
247
+ 1
237
248
  ```
238
249
 
239
250
  ## Enum Definitions
240
251
 
241
252
  `FieldDef.enum_def_name` holds only the name of an `EnumDef`, not the definition
242
- itself. Pass the actual definitions via an `enum_def_dict` (mapping name to
243
- `EnumDef`) to `decode` / `encode` so the matching `EnumDef` can be resolved and
244
- attached as `FieldInstance.enum_item`.
253
+ itself. In practice, `TypeDict` is most useful when you pass both
254
+ `type_dict.struct_dict` and `type_dict.enum_dict` together: one resolves named
255
+ layout types and the other resolves enum labels. The example below uses both in
256
+ one decode flow.
245
257
 
246
258
  ```python
247
259
  from sltcore import InfoSize
248
- from sltcodec import EnumDef, FieldDef, decode
260
+ from sltcodec import EnumDef, EnumDict, FieldDef, StructDef, StructDict, TypeDict, decode
261
+
262
+ enum_dict = EnumDict({"Status": EnumDef(name="Status", values={"OK": 0, "NG": 1})})
263
+
264
+ packet_struct = StructDef(
265
+ name="Packet",
266
+ fields=[
267
+ FieldDef(name="status",
268
+ offset=InfoSize(0, 0),
269
+ size=InfoSize(1, 0),
270
+ type="unsigned int",
271
+ enum_def_name="Status"),
272
+ ],
273
+ )
249
274
 
250
- status_enum = EnumDef(name="Status", values={"OK": 0, "NG": 1})
275
+ struct_dict = StructDict({"Packet": packet_struct})
251
276
 
252
- struct_def = [
253
- FieldDef(name="status",
254
- offset=InfoSize(0, 0),
255
- size=InfoSize(1, 0),
256
- type="unsigned int",
257
- enum_def_name="Status"),
258
- ]
277
+ type_dict = TypeDict(struct_dict=struct_dict.items_dict(),
278
+ enum_dict=enum_dict.items_dict())
259
279
 
260
- decoded = decode(struct_def, bytearray(b"\x01"),
261
- enum_def_dict={"Status": status_enum})
280
+ decoded = decode(type_dict.struct_dict["Packet"],
281
+ bytearray(b"\x01"),
282
+ type_dict=type_dict)
262
283
 
263
284
  print(decoded.field_instances[0].value)
264
285
  print(decoded.field_instances[0].enum_item)
@@ -271,8 +292,8 @@ Output:
271
292
  ('NG', 1)
272
293
  ```
273
294
 
274
- You can also persist enum definitions by name with
275
- `save_enum_def_dict` / `load_enum_def_dict`, mirroring `StructDef` dictionaries.
295
+ You can also persist enum definitions by name through `TypeDict` using
296
+ `save_type_dict` / `load_type_dict`.
276
297
 
277
298
  ## Development
278
299
 
@@ -128,7 +128,7 @@ Output:
128
128
  ```python
129
129
  bytearray(b"\x03\x04")
130
130
  StructInstance(struct_def=StructDef(...), field_instances=[...])
131
- [FieldInstance(field_def=FieldDef(name='pair', offset=InfoSize(byte=0, bit=0), size=InfoSize(byte=2, bit=0), type=[...], scale=1.0, repeat=None),
131
+ [FieldInstance(field_def=FieldDef(name='pair', offset=InfoSize(byte=0, bit=0), size=InfoSize(byte=2, bit=0), type=StructDef(...), scale=1.0, repeat=None),
132
132
  value=StructInstance(struct_def=StructDef(...), field_instances=[...]))]
133
133
  ```
134
134
 
@@ -182,66 +182,87 @@ print(encoded_int, decoded_int)
182
182
  print(encoded_float, decoded_float)
183
183
  ```
184
184
 
185
- ## Saving And Loading StructDef Dictionaries
185
+ ## Saving And Loading Type Dictionaries
186
186
 
187
- You can persist reusable structure definitions by name with
188
- `save_struct_def_dict` / `load_struct_def_dict`.
187
+ You can persist reusable structure and enum definitions together via
188
+ `TypeDict` with `save_type_dict` / `load_type_dict`.
189
+
190
+ `InfoSize` values in `offset` and `size` are saved as typed dictionaries in
191
+ JSON. Expression-based offsets and sizes remain strings and are resolved when
192
+ the definition is used.
189
193
 
190
194
  ```python
191
195
  from pathlib import Path
192
196
 
193
197
  from sltcore import InfoSize
194
- from sltcodec import FieldDef, StructDef, load_struct_def_dict, save_struct_def_dict
195
-
196
- struct_defs = {
197
- "Header": StructDef(
198
- name="Header",
199
- description="Simple header",
200
- fields=[
201
- FieldDef(name="kind", offset=InfoSize(0, 0), size=InfoSize(1, 0), type="unsigned int"),
202
- FieldDef(name="flags", offset=InfoSize(1, 0), size=InfoSize(1, 0), type="unsigned int"),
203
- ],
204
- )
205
- }
198
+ from sltcodec import EnumDef, FieldDef, StructDef, TypeDict, load_type_dict, save_type_dict
199
+
200
+ type_dict = TypeDict(
201
+ struct_dict={
202
+ "Header": StructDef(
203
+ name="Header",
204
+ description="Simple header",
205
+ fields=[
206
+ FieldDef(name="kind", offset=InfoSize(0, 0), size=InfoSize(1, 0), type="unsigned int"),
207
+ FieldDef(name="flags", offset=InfoSize(1, 0), size=InfoSize(1, 0), type="unsigned int"),
208
+ ],
209
+ )
210
+ },
211
+ enum_dict={
212
+ "Status": EnumDef(name="Status", values={"OK": 0, "NG": 1}),
213
+ }
214
+ )
206
215
 
207
216
  path = Path("struct_defs.json")
208
- save_struct_def_dict(path, struct_defs)
209
- loaded = load_struct_def_dict(path)
217
+ save_type_dict(path, type_dict)
218
+ loaded = load_type_dict(path)
210
219
 
211
- print(loaded["Header"].name)
212
- print(loaded["Header"].description)
220
+ print(loaded.struct_dict["Header"].name)
221
+ print(loaded.struct_dict["Header"].description)
222
+ print(loaded.enum_dict["Status"].values["NG"])
213
223
  ```
214
224
 
215
225
  Example output:
216
226
 
217
227
  ```python
218
- bytearray(b"\x01\x07") StructInstance(struct_def=StructDef(...), field_instances=[...])
219
- bytearray(b"\x02?\xc0\x00\x00") StructInstance(struct_def=StructDef(...), field_instances=[...])
228
+ Header
229
+ Simple header
230
+ 1
220
231
  ```
221
232
 
222
233
  ## Enum Definitions
223
234
 
224
235
  `FieldDef.enum_def_name` holds only the name of an `EnumDef`, not the definition
225
- itself. Pass the actual definitions via an `enum_def_dict` (mapping name to
226
- `EnumDef`) to `decode` / `encode` so the matching `EnumDef` can be resolved and
227
- attached as `FieldInstance.enum_item`.
236
+ itself. In practice, `TypeDict` is most useful when you pass both
237
+ `type_dict.struct_dict` and `type_dict.enum_dict` together: one resolves named
238
+ layout types and the other resolves enum labels. The example below uses both in
239
+ one decode flow.
228
240
 
229
241
  ```python
230
242
  from sltcore import InfoSize
231
- from sltcodec import EnumDef, FieldDef, decode
243
+ from sltcodec import EnumDef, EnumDict, FieldDef, StructDef, StructDict, TypeDict, decode
244
+
245
+ enum_dict = EnumDict({"Status": EnumDef(name="Status", values={"OK": 0, "NG": 1})})
246
+
247
+ packet_struct = StructDef(
248
+ name="Packet",
249
+ fields=[
250
+ FieldDef(name="status",
251
+ offset=InfoSize(0, 0),
252
+ size=InfoSize(1, 0),
253
+ type="unsigned int",
254
+ enum_def_name="Status"),
255
+ ],
256
+ )
232
257
 
233
- status_enum = EnumDef(name="Status", values={"OK": 0, "NG": 1})
258
+ struct_dict = StructDict({"Packet": packet_struct})
234
259
 
235
- struct_def = [
236
- FieldDef(name="status",
237
- offset=InfoSize(0, 0),
238
- size=InfoSize(1, 0),
239
- type="unsigned int",
240
- enum_def_name="Status"),
241
- ]
260
+ type_dict = TypeDict(struct_dict=struct_dict.items_dict(),
261
+ enum_dict=enum_dict.items_dict())
242
262
 
243
- decoded = decode(struct_def, bytearray(b"\x01"),
244
- enum_def_dict={"Status": status_enum})
263
+ decoded = decode(type_dict.struct_dict["Packet"],
264
+ bytearray(b"\x01"),
265
+ type_dict=type_dict)
245
266
 
246
267
  print(decoded.field_instances[0].value)
247
268
  print(decoded.field_instances[0].enum_item)
@@ -254,8 +275,8 @@ Output:
254
275
  ('NG', 1)
255
276
  ```
256
277
 
257
- You can also persist enum definitions by name with
258
- `save_enum_def_dict` / `load_enum_def_dict`, mirroring `StructDef` dictionaries.
278
+ You can also persist enum definitions by name through `TypeDict` using
279
+ `save_type_dict` / `load_type_dict`.
259
280
 
260
281
  ## Development
261
282
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "sltcodec"
3
- version = "1.2.0"
3
+ version = "1.3.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"
@@ -0,0 +1,21 @@
1
+ from .codec import (PRIMITIVE_TYPES, decode, encode, encode_field,
2
+ load_type_dict, save_type_dict)
3
+ from .types import (EnumDef, EnumDict, FieldDef, FieldInstance, StructDef,
4
+ StructDict, StructInstance, TypeDict)
5
+
6
+ __all__ = [
7
+ "PRIMITIVE_TYPES",
8
+ "EnumDef",
9
+ "EnumDict",
10
+ "FieldDef",
11
+ "FieldInstance",
12
+ "StructDef",
13
+ "StructDict",
14
+ "StructInstance",
15
+ "TypeDict",
16
+ "decode",
17
+ "encode",
18
+ "encode_field",
19
+ "load_type_dict",
20
+ "save_type_dict",
21
+ ]
@@ -8,7 +8,8 @@ from typing import Any
8
8
  from sltcalc import SltEval
9
9
  from sltcore import Info, InfoSize, bits_get, bits_set
10
10
 
11
- from .types import EnumDef, FieldDef, FieldInstance, StructDef, StructInstance
11
+ from .types import (EnumDef, FieldDef, FieldInstance, StructDef, StructInstance,
12
+ TypeDict)
12
13
 
13
14
  PRIMITIVE_TYPES = {
14
15
  "bool",
@@ -46,44 +47,78 @@ def _validate_struct_instance(struct_instance: StructInstance) -> None:
46
47
  f"{type(field_instance).__name__}")
47
48
 
48
49
 
49
- def save_struct_def_dict(path: str | Path,
50
- struct_def_dict: dict[str, StructDef]) -> None:
51
- """Save a structure definition dictionary to a JSON file."""
50
+ def save_type_dict(path: str | Path, type_dict: TypeDict) -> None:
51
+ """Save TypeDict to a JSON file."""
52
52
  payload = {
53
- name: struct_def.to_dict()
54
- for name, struct_def in struct_def_dict.items()
53
+ "struct_dict": {
54
+ name: struct_def.to_dict()
55
+ for name, struct_def in type_dict.struct_dict.items()
56
+ },
57
+ "enum_dict": {
58
+ name: enum_def.to_dict()
59
+ for name, enum_def in type_dict.enum_dict.items()
60
+ },
55
61
  }
56
62
  Path(path).write_text(json.dumps(payload, ensure_ascii=False, indent=2),
57
63
  encoding="utf-8")
58
64
 
59
65
 
66
+ def load_type_dict(path: str | Path) -> TypeDict:
67
+ """Load TypeDict from a JSON file."""
68
+ data = json.loads(Path(path).read_text(encoding="utf-8"))
69
+
70
+ struct_payload = data.get("struct_dict")
71
+ enum_payload = data.get("enum_dict")
72
+
73
+ if isinstance(struct_payload, dict) and isinstance(enum_payload, dict):
74
+ return TypeDict(
75
+ struct_dict={
76
+ name: StructDef.from_dict(struct_def_data)
77
+ for name, struct_def_data in struct_payload.items()
78
+ },
79
+ enum_dict={
80
+ name: EnumDef.from_dict(enum_def_data)
81
+ for name, enum_def_data in enum_payload.items()
82
+ },
83
+ )
84
+
85
+ # Backward compatibility: old files that stored only struct or enum maps.
86
+ legacy_entries: dict[str, Any] = data if isinstance(data, dict) else {}
87
+ if any(
88
+ isinstance(item, dict) and "fields" in item
89
+ for item in legacy_entries.values()):
90
+ return TypeDict(
91
+ struct_dict={
92
+ name: StructDef.from_dict(struct_def_data)
93
+ for name, struct_def_data in legacy_entries.items()
94
+ })
95
+ return TypeDict(
96
+ enum_dict={
97
+ name: EnumDef.from_dict(enum_def_data)
98
+ for name, enum_def_data in legacy_entries.items()
99
+ })
100
+
101
+
102
+ def save_struct_def_dict(path: str | Path,
103
+ struct_def_dict: dict[str, StructDef]) -> None:
104
+ """Save a structure definition dictionary to a JSON file."""
105
+ save_type_dict(path, TypeDict(struct_dict=struct_def_dict))
106
+
107
+
60
108
  def load_struct_def_dict(path: str | Path) -> dict[str, StructDef]:
61
109
  """Load a structure definition dictionary from a JSON file."""
62
- data = json.loads(Path(path).read_text(encoding="utf-8"))
63
- return {
64
- name: StructDef.from_dict(field_def_data)
65
- for name, field_def_data in data.items()
66
- }
110
+ return load_type_dict(path).struct_dict.items_dict()
67
111
 
68
112
 
69
113
  def save_enum_def_dict(path: str | Path, enum_def_dict: dict[str,
70
114
  EnumDef]) -> None:
71
115
  """Save an enum definition dictionary to a JSON file."""
72
- payload = {
73
- name: enum_def.to_dict()
74
- for name, enum_def in enum_def_dict.items()
75
- }
76
- Path(path).write_text(json.dumps(payload, ensure_ascii=False, indent=2),
77
- encoding="utf-8")
116
+ save_type_dict(path, TypeDict(enum_dict=enum_def_dict))
78
117
 
79
118
 
80
119
  def load_enum_def_dict(path: str | Path) -> dict[str, EnumDef]:
81
120
  """Load an enum definition dictionary from a JSON file."""
82
- data = json.loads(Path(path).read_text(encoding="utf-8"))
83
- return {
84
- name: EnumDef.from_dict(enum_def_data)
85
- for name, enum_def_data in data.items()
86
- }
121
+ return load_type_dict(path).enum_dict.items_dict()
87
122
 
88
123
 
89
124
  def _resolve_info_size(value: InfoSize | str, env: dict[str, Any]) -> InfoSize:
@@ -144,7 +179,7 @@ def _validate_padding_alignment_bits(padding_alignment_bits: int) -> None:
144
179
 
145
180
 
146
181
  def _resolve_field_type(field_type: str | StructDef,
147
- struct_def_dict: dict[str, StructDef] | None = None,
182
+ type_dict: TypeDict | None = None,
148
183
  env: dict[str, Any] | None = None) -> str | StructDef:
149
184
  """Resolve a field type that can be primitive, named, or nested."""
150
185
  if isinstance(field_type, StructDef):
@@ -152,7 +187,7 @@ def _resolve_field_type(field_type: str | StructDef,
152
187
  if field_type in _PRIMITIVE_TYPES:
153
188
  return field_type
154
189
 
155
- resolved_struct_def = _get_struct_def(field_type, struct_def_dict, env)
190
+ resolved_struct_def = _get_struct_def(field_type, type_dict, env)
156
191
  if isinstance(resolved_struct_def, StructDef):
157
192
  return resolved_struct_def
158
193
  if (isinstance(resolved_struct_def, str)
@@ -188,8 +223,7 @@ def _prepare_field_info(
188
223
  field_def: FieldDef,
189
224
  value: Any,
190
225
  env: dict[str, Any],
191
- struct_def_dict: dict[str, StructDef] | None = None,
192
- enum_def_dict: dict[str, EnumDef] | None = None,
226
+ type_dict: TypeDict | None = None,
193
227
  padding_alignment_bits: int = _DEFAULT_PADDING_ALIGNMENT_BITS,
194
228
  ) -> tuple[InfoSize, InfoSize, Info] | None:
195
229
  """Resolve a field definition into an offset, size, and info payload."""
@@ -198,7 +232,7 @@ def _prepare_field_info(
198
232
  if size.byte == 0 and size.bit == 0:
199
233
  return None
200
234
 
201
- resolved_type = _resolve_field_type(field_def.type, struct_def_dict, env)
235
+ resolved_type = _resolve_field_type(field_def.type, type_dict, env)
202
236
  if isinstance(resolved_type, StructDef):
203
237
  if not isinstance(value, StructInstance):
204
238
  raise TypeError(
@@ -206,8 +240,7 @@ def _prepare_field_info(
206
240
  "values")
207
241
  nested_bytes = encode(value,
208
242
  bytearray(),
209
- struct_def_dict,
210
- enum_def_dict,
243
+ type_dict,
211
244
  padding_alignment_bits=padding_alignment_bits)
212
245
  info = Info.from_bytes(bytes(nested_bytes), size, scale=field_def.scale)
213
246
  else:
@@ -233,16 +266,15 @@ def encode_field(
233
266
  value: Any,
234
267
  buf: bytearray,
235
268
  env: dict[str, Any] | None = None,
236
- struct_def_dict: dict[str, StructDef] | None = None,
237
- enum_def_dict: dict[str, EnumDef] | None = None,
269
+ type_dict: TypeDict | None = None,
238
270
  padding_alignment_bits: int = _DEFAULT_PADDING_ALIGNMENT_BITS,
239
271
  ) -> None:
240
272
  """Encode a single field into a bytearray."""
241
273
  if env is None:
242
274
  env = {}
243
275
 
244
- prepared = _prepare_field_info(field_def, value, env, struct_def_dict,
245
- enum_def_dict, padding_alignment_bits)
276
+ prepared = _prepare_field_info(field_def, value, env, type_dict,
277
+ padding_alignment_bits)
246
278
  if prepared is None:
247
279
  return
248
280
 
@@ -259,8 +291,7 @@ def encode_field(
259
291
  def encode(
260
292
  struct_instance: StructInstance,
261
293
  buf: bytearray,
262
- struct_def_dict: dict[str, StructDef] | None = None,
263
- enum_def_dict: dict[str, EnumDef] | None = None,
294
+ type_dict: TypeDict | None = None,
264
295
  padding_alignment_bits: int = _DEFAULT_PADDING_ALIGNMENT_BITS,
265
296
  ) -> bytearray:
266
297
  """Encode decode() result into a bytearray.
@@ -271,10 +302,9 @@ def encode(
271
302
  The structure instance to encode.
272
303
  buf : bytearray
273
304
  The base bytearray instance to write into.
274
- struct_def_dict : dict[str, StructDef] | None, optional
275
- A dictionary of structure definitions, by default None.
276
- enum_def_dict : dict[str, EnumDef] | None, optional
277
- A dictionary of enum definitions, by default None.
305
+ type_dict : TypeDict | None, optional
306
+ Type dictionaries including structure and enum definitions,
307
+ by default None.
278
308
  padding_alignment_bits : int, optional
279
309
  The padding alignment boundary in bits, by default 32.
280
310
 
@@ -304,8 +334,7 @@ def encode(
304
334
  for i in range(field_def.repeat):
305
335
  field_def_repeat = _split_repeated_field(
306
336
  field_def, i, env, current_offset)
307
- encode_field(field_def_repeat, values[i], buf, env,
308
- struct_def_dict, enum_def_dict,
337
+ encode_field(field_def_repeat, values[i], buf, env, type_dict,
309
338
  padding_alignment_bits)
310
339
  env[field_def_repeat.name] = values[i]
311
340
  if isinstance(current_offset, InfoSize) and isinstance(
@@ -313,7 +342,7 @@ def encode(
313
342
  current_offset += resolved_size
314
343
  continue
315
344
 
316
- encode_field(field_def, value, buf, env, struct_def_dict, enum_def_dict,
345
+ encode_field(field_def, value, buf, env, type_dict,
317
346
  padding_alignment_bits)
318
347
  env[field_def.name] = value
319
348
 
@@ -327,8 +356,7 @@ def decode_field(
327
356
  field_def: FieldDef,
328
357
  data: bytearray | bytes,
329
358
  env: dict[str, Any] | None = None,
330
- struct_def_dict: dict[str, StructDef] | None = None,
331
- enum_def_dict: dict[str, EnumDef] | None = None,
359
+ type_dict: TypeDict | None = None,
332
360
  padding_alignment_bits: int = _DEFAULT_PADDING_ALIGNMENT_BITS,
333
361
  ) -> FieldInstance | None:
334
362
  """Decode a single field from a bytearray according to a field definition.
@@ -341,8 +369,9 @@ def decode_field(
341
369
  The data to decode.
342
370
  env : dict[str, Any] | None, optional
343
371
  The environment for evaluating expressions, by default None.
344
- struct_def_dict : dict[str, StructDef] | None, optional
345
- A dictionary of structure definitions, by default None.
372
+ type_dict : TypeDict | None, optional
373
+ Type dictionaries including structure and enum definitions,
374
+ by default None.
346
375
  Returns
347
376
  -------
348
377
  FieldInstance | None
@@ -358,7 +387,7 @@ def decode_field(
358
387
  byte_swap = _resolve_byte_swap(field_def, env)
359
388
  if byte_swap:
360
389
  info = _byte_swap_info(info, size)
361
- resolved_type = _resolve_field_type(field_def.type, struct_def_dict, env)
390
+ resolved_type = _resolve_field_type(field_def.type, type_dict, env)
362
391
  resolved_field_def = FieldDef(
363
392
  name=field_def.name,
364
393
  offset=offset,
@@ -374,40 +403,38 @@ def decode_field(
374
403
  if isinstance(resolved_type, StructDef):
375
404
  return FieldInstance(
376
405
  field_def=resolved_field_def,
377
- value=decode(resolved_type, bytearray(info.to_bytes),
378
- struct_def_dict, enum_def_dict,
406
+ value=decode(resolved_type, bytearray(info.to_bytes), type_dict,
379
407
  padding_alignment_bits),
380
408
  )
381
409
  if resolved_type == "bool":
382
410
  return FieldInstance.from_value(resolved_field_def,
383
411
  info.to_bool,
384
- enum_def_dict=enum_def_dict)
412
+ type_dict=type_dict)
385
413
  if resolved_type == "signed int":
386
414
  return FieldInstance.from_value(resolved_field_def,
387
415
  info.to_signed_int,
388
- enum_def_dict=enum_def_dict)
416
+ type_dict=type_dict)
389
417
  if resolved_type in ["int", "unsigned int"]:
390
418
  return FieldInstance.from_value(resolved_field_def,
391
419
  info.to_unsigned_int,
392
- enum_def_dict=enum_def_dict)
420
+ type_dict=type_dict)
393
421
  if resolved_type == "float":
394
422
  return FieldInstance.from_value(resolved_field_def,
395
423
  info.to_float,
396
- enum_def_dict=enum_def_dict)
424
+ type_dict=type_dict)
397
425
  if resolved_type in ["bytearray", "bytes"]:
398
426
  return FieldInstance.from_value(resolved_field_def,
399
427
  info.to_bytes,
400
- enum_def_dict=enum_def_dict)
428
+ type_dict=type_dict)
401
429
  return FieldInstance.from_value(resolved_field_def,
402
430
  info.raw_value,
403
- enum_def_dict=enum_def_dict)
431
+ type_dict=type_dict)
404
432
 
405
433
 
406
434
  def decode(
407
435
  struct_def: StructDef | list[FieldDef],
408
436
  data: bytearray | bytes,
409
- struct_def_dict: dict[str, StructDef] | None = None,
410
- enum_def_dict: dict[str, EnumDef] | None = None,
437
+ type_dict: TypeDict | None = None,
411
438
  padding_alignment_bits: int = _DEFAULT_PADDING_ALIGNMENT_BITS,
412
439
  ) -> StructInstance:
413
440
  """Decode a bytearray into field values according to a layout.
@@ -418,10 +445,9 @@ def decode(
418
445
  The definitions of the fields to decode.
419
446
  data : bytearray | bytes
420
447
  The data to decode.
421
- struct_def_dict : dict[str, StructDef] | None, optional
422
- A dictionary of structure definitions, by default None.
423
- enum_def_dict : dict[str, EnumDef] | None, optional
424
- A dictionary of enum definitions, by default None.
448
+ type_dict : TypeDict | None, optional
449
+ Type dictionaries including structure and enum definitions,
450
+ by default None.
425
451
  padding_alignment_bits : int, optional
426
452
  The padding alignment boundary in bits, by default 32.
427
453
  Returns
@@ -471,8 +497,8 @@ def decode(
471
497
  if field_def.repeat is None or field_def.repeat <= 1:
472
498
  resolved_offset = _resolve_offset(field_def, env)
473
499
  append_padding_until(resolved_offset)
474
- field_instance = decode_field(field_def, data, env, struct_def_dict,
475
- enum_def_dict, padding_alignment_bits)
500
+ field_instance = decode_field(field_def, data, env, type_dict,
501
+ padding_alignment_bits)
476
502
  if field_instance is not None:
477
503
  env[field_instance.field_def.name] = field_instance.value
478
504
  result.append_field_instance(field_instance)
@@ -487,8 +513,7 @@ def decode(
487
513
  field_def_repeat = _split_repeated_field(field_def, i, env,
488
514
  current_offset)
489
515
  field_instance = decode_field(field_def_repeat, data, env,
490
- struct_def_dict, enum_def_dict,
491
- padding_alignment_bits)
516
+ type_dict, padding_alignment_bits)
492
517
  if field_instance is not None:
493
518
  env[field_instance.field_def.name] = field_instance.value
494
519
  result.append_field_instance(field_instance)
@@ -501,17 +526,19 @@ def decode(
501
526
 
502
527
 
503
528
  def _get_struct_def(
504
- struct_def_name: str,
505
- struct_def_dict: dict[str, StructDef] | None = None,
506
- env: dict[str, Any] | None = None) -> StructDef | str | None:
529
+ struct_def_name: str,
530
+ type_dict: TypeDict | None = None,
531
+ env: dict[str, Any] | None = None,
532
+ ) -> StructDef | str | None:
507
533
  """Get a structure definition from a dictionary or evaluate it.
508
534
 
509
535
  Parameters
510
536
  ----------
511
537
  struct_def_name : str
512
538
  The name of the structure definition to get.
513
- struct_def_dict : dict[str, StructDef] | None, optional
514
- A dictionary of structure definitions, by default None.
539
+ type_dict : TypeDict | None, optional
540
+ Type dictionaries including structure and enum definitions,
541
+ by default None.
515
542
  env : dict[str, Any] | None, optional
516
543
  The environment for evaluating the structure definition,
517
544
  by default None.
@@ -521,8 +548,10 @@ def _get_struct_def(
521
548
  StructDef | str | None
522
549
  The structure definition, primitive type, or None if not found.
523
550
  """
524
- if struct_def_dict and struct_def_name in struct_def_dict:
525
- return struct_def_dict[struct_def_name]
551
+ struct_dict = None if type_dict is None else type_dict.struct_dict
552
+
553
+ if struct_dict and struct_def_name in struct_dict:
554
+ return struct_dict[struct_def_name]
526
555
 
527
556
  stleval = SltEval(env)
528
557
  try:
@@ -532,7 +561,7 @@ def _get_struct_def(
532
561
 
533
562
  if isinstance(eval_result, str) and eval_result in _PRIMITIVE_TYPES:
534
563
  return eval_result
535
- if (isinstance(eval_result, str) and struct_def_dict
536
- and eval_result in struct_def_dict):
537
- return struct_def_dict[eval_result]
564
+ if (isinstance(eval_result, str) and struct_dict
565
+ and eval_result in struct_dict):
566
+ return struct_dict[eval_result]
538
567
  return None
@@ -3,9 +3,10 @@ from __future__ import annotations
3
3
 
4
4
  import json
5
5
  import re
6
+ from collections.abc import MutableMapping
6
7
  from dataclasses import dataclass, field
7
8
  from functools import total_ordering
8
- from typing import Any
9
+ from typing import Any, Dict, Iterator, Optional
9
10
 
10
11
  from sltcalc import SltEval
11
12
  from sltcore import Info, InfoSize
@@ -159,17 +160,19 @@ class FieldDef:
159
160
 
160
161
  def to_dict(self) -> dict[str, Any]:
161
162
  """Convert this field definition to a JSON-serializable dictionary."""
163
+
164
+ def _serialize_info_like(value: Any) -> Any:
165
+ if isinstance(value, (Info, InfoSize)):
166
+ return json.loads(value.to_json())
167
+ return value
168
+
162
169
  return {
163
170
  "name":
164
171
  self.name,
165
172
  "offset":
166
- self.offset.serialize() if isinstance(self.offset,
167
- (Info,
168
- InfoSize)) else self.offset,
173
+ _serialize_info_like(self.offset),
169
174
  "size":
170
- self.size.serialize() if isinstance(self.size,
171
- (Info,
172
- InfoSize)) else self.size,
175
+ _serialize_info_like(self.size),
173
176
  "type": {
174
177
  "__type__": "StructDef",
175
178
  "fields": self.type.to_dict(),
@@ -334,10 +337,10 @@ class FieldInstance:
334
337
  def from_value(cls,
335
338
  field_def: FieldDef,
336
339
  value: Any,
337
- enum_def_dict: dict[str, EnumDef] | None = None,
340
+ type_dict: "TypeDict | None" = None,
338
341
  is_padding: bool = False) -> "FieldInstance":
339
342
  """Create a FieldInstance and attach a matched enum item if any."""
340
- enum_def = cls._resolve_enum_def(field_def, enum_def_dict)
343
+ enum_def = cls._resolve_enum_def(field_def, type_dict)
341
344
  enum_item = cls._resolve_enum_item(enum_def, value)
342
345
  return cls(field_def=field_def,
343
346
  value=value,
@@ -345,17 +348,19 @@ class FieldInstance:
345
348
  is_padding=is_padding)
346
349
 
347
350
  @staticmethod
348
- def _resolve_enum_def(
349
- field_def: FieldDef,
350
- enum_def_dict: dict[str, EnumDef] | None) -> EnumDef | None:
351
+ def _resolve_enum_def(field_def: FieldDef,
352
+ type_dict: "TypeDict | None") -> EnumDef | None:
351
353
  """Resolve enum definition from field metadata or lookup dictionary."""
352
- if not enum_def_dict:
354
+ if type_dict is None:
355
+ return None
356
+ enum_dict = type_dict.enum_dict
357
+ if len(enum_dict) == 0:
353
358
  return None
354
359
  if field_def.enum_def_name is not None:
355
- return enum_def_dict.get(field_def.enum_def_name)
356
- if isinstance(field_def.type, str) and field_def.type in enum_def_dict:
357
- return enum_def_dict[field_def.type]
358
- return enum_def_dict.get(field_def.name)
360
+ return enum_dict.get(field_def.enum_def_name)
361
+ if isinstance(field_def.type, str) and field_def.type in enum_dict:
362
+ return enum_dict[field_def.type]
363
+ return enum_dict.get(field_def.name)
359
364
 
360
365
  @staticmethod
361
366
  def _resolve_enum_item(enum_def: EnumDef | None,
@@ -594,3 +599,82 @@ class StructInstance:
594
599
 
595
600
  if max_end_offset > self.size:
596
601
  self.size = max_end_offset
602
+
603
+
604
+ @dataclass
605
+ class EnumDict(MutableMapping):
606
+ """A dictionary-like container for EnumDef objects."""
607
+ _items: Dict[str, "EnumDef"]
608
+
609
+ def __init__(self, items: Optional[Dict[str, "EnumDef"]] = None):
610
+ self._items = items or {}
611
+
612
+ def __getitem__(self, key: str) -> "EnumDef":
613
+ return self._items[key]
614
+
615
+ def __setitem__(self, key: str, value: "EnumDef"):
616
+ self._items[key] = value
617
+
618
+ def __delitem__(self, key: str):
619
+ del self._items[key]
620
+
621
+ def __iter__(self) -> Iterator[str]:
622
+ return iter(self._items)
623
+
624
+ def __len__(self) -> int:
625
+ return len(self._items)
626
+
627
+ def items_dict(self) -> Dict[str, "EnumDef"]:
628
+ """Return the underlying dictionary of EnumDef objects.
629
+ Returns:
630
+ Dict[str, EnumDef]: The underlying dictionary
631
+ of EnumDef objects."""
632
+ return self._items
633
+
634
+
635
+ @dataclass
636
+ class StructDict(MutableMapping):
637
+ """A dictionary-like container for StructDef objects. """
638
+ _items: Dict[str, "StructDef"]
639
+
640
+ def __init__(self, items: Optional[Dict[str, "StructDef"]] = None):
641
+ self._items = items or {}
642
+
643
+ def __getitem__(self, key: str) -> "StructDef":
644
+ return self._items[key]
645
+
646
+ def __setitem__(self, key: str, value: "StructDef"):
647
+ self._items[key] = value
648
+
649
+ def __delitem__(self, key: str):
650
+ del self._items[key]
651
+
652
+ def __iter__(self) -> Iterator[str]:
653
+ return iter(self._items)
654
+
655
+ def __len__(self) -> int:
656
+ return len(self._items)
657
+
658
+ def items_dict(self) -> Dict[str, "StructDef"]:
659
+ """Return the underlying dictionary of StructDef objects.
660
+ Returns:
661
+ Dict[str, StructDef]: The underlying dictionary
662
+ of StructDef objects."""
663
+ return self._items
664
+
665
+
666
+ @dataclass
667
+ class TypeDict:
668
+ """A dictionary-like container for EnumDef and StructDef objects."""
669
+ enum_dict: EnumDict
670
+ struct_dict: StructDict
671
+
672
+ def __init__(
673
+ self,
674
+ enum_dict: Optional[Dict[str, "EnumDef"]] = None,
675
+ struct_dict: Optional[Dict[str, "StructDef"]] = None,
676
+ ):
677
+ """Initialize TypeDict with optional EnumDef
678
+ and StructDef dictionaries."""
679
+ self.enum_dict = EnumDict(enum_dict)
680
+ self.struct_dict = StructDict(struct_dict)
@@ -1,4 +1,5 @@
1
1
  """Tests for the sltcodec module."""
2
+ import json
2
3
  from enum import Enum
3
4
  from pathlib import Path
4
5
 
@@ -6,9 +7,8 @@ import pytest
6
7
  from sltcore import InfoSize
7
8
 
8
9
  from sltcodec import (PRIMITIVE_TYPES, EnumDef, FieldDef, FieldInstance,
9
- StructDef, StructInstance, decode, encode,
10
- load_enum_def_dict, load_struct_def_dict,
11
- save_enum_def_dict, save_struct_def_dict)
10
+ StructDef, StructInstance, TypeDict, decode, encode,
11
+ load_type_dict, save_type_dict)
12
12
  from sltcodec.codec import decode_field
13
13
 
14
14
 
@@ -401,10 +401,9 @@ def test_decode_uses_enum_def_dict_to_set_enum_item():
401
401
  offset=InfoSize(0, 0),
402
402
  size=InfoSize(1, 0),
403
403
  type="unsigned int")
404
+ type_dict = TypeDict(enum_dict={"status": enum_def})
404
405
 
405
- decoded = decode([field_def],
406
- bytearray(b"\x02"),
407
- enum_def_dict={"status": enum_def})
406
+ decoded = decode([field_def], bytearray(b"\x02"), type_dict=type_dict)
408
407
 
409
408
  assert decoded.field_instances[0].value == 2
410
409
  assert decoded.field_instances[0].enum_item == ("NG", 2)
@@ -420,7 +419,7 @@ def test_field_instance_from_value_sets_enum_item_from_field_enum_def():
420
419
  enum_def_name=enum_def.name)
421
420
 
422
421
  field_instance = FieldInstance.from_value(
423
- field_def, 1, enum_def_dict={enum_def.name: enum_def})
422
+ field_def, 1, type_dict=TypeDict(enum_dict={enum_def.name: enum_def}))
424
423
 
425
424
  assert field_instance.enum_item == ("MANUAL", 1)
426
425
 
@@ -459,8 +458,8 @@ def test_struct_instance_size_json_round_trip():
459
458
  assert struct_instance.size == InfoSize(8, 0)
460
459
 
461
460
 
462
- def test_struct_def_dict_load_and_save(tmp_path: Path):
463
- """Test that a StructDef dictionary can be saved and reloaded."""
461
+ def test_type_dict_load_and_save_struct_dict(tmp_path: Path):
462
+ """Test TypeDict save/load for struct definitions."""
464
463
  struct_def = StructDef(name="ValueStruct",
465
464
  description="Single value struct",
466
465
  fields=[
@@ -472,14 +471,19 @@ def test_struct_def_dict_load_and_save(tmp_path: Path):
472
471
  ])
473
472
  path = tmp_path / "field_defs.json"
474
473
 
475
- save_struct_def_dict(path, {"value": struct_def})
476
- loaded = load_struct_def_dict(path)
474
+ save_type_dict(path, TypeDict(struct_dict={"value": struct_def}))
475
+ saved_field = json.loads(
476
+ path.read_text())["struct_dict"]["value"]["fields"][0]
477
477
 
478
- assert loaded["value"] == struct_def
478
+ assert isinstance(saved_field["offset"], dict)
479
+ assert isinstance(saved_field["size"], dict)
480
+ loaded = load_type_dict(path)
479
481
 
482
+ assert loaded.struct_dict["value"] == struct_def
480
483
 
481
- def test_enum_def_dict_load_and_save(tmp_path: Path):
482
- """Test that an EnumDef dictionary can be saved and reloaded."""
484
+
485
+ def test_type_dict_load_and_save_enum_dict(tmp_path: Path):
486
+ """Test TypeDict save/load for enum definitions."""
483
487
  enum_def = EnumDef(name="Status",
484
488
  description="Status enum",
485
489
  values={
@@ -488,10 +492,10 @@ def test_enum_def_dict_load_and_save(tmp_path: Path):
488
492
  })
489
493
  path = tmp_path / "enum_defs.json"
490
494
 
491
- save_enum_def_dict(path, {"status": enum_def})
492
- loaded = load_enum_def_dict(path)
495
+ save_type_dict(path, TypeDict(enum_dict={"status": enum_def}))
496
+ loaded = load_type_dict(path)
493
497
 
494
- assert loaded["status"] == enum_def
498
+ assert loaded.enum_dict["status"] == enum_def
495
499
 
496
500
 
497
501
  def test_struct_def_to_json_from_json_round_trip():
@@ -74,7 +74,7 @@ wheels = [
74
74
 
75
75
  [[package]]
76
76
  name = "sltcodec"
77
- version = "1.1.0"
77
+ version = "1.2.1"
78
78
  source = { editable = "." }
79
79
  dependencies = [
80
80
  { name = "sltcalc" },
@@ -1,20 +0,0 @@
1
- from .codec import (PRIMITIVE_TYPES, decode, encode, encode_field,
2
- load_enum_def_dict, load_struct_def_dict,
3
- save_enum_def_dict, save_struct_def_dict)
4
- from .types import EnumDef, FieldDef, FieldInstance, StructDef, StructInstance
5
-
6
- __all__ = [
7
- "PRIMITIVE_TYPES",
8
- "EnumDef",
9
- "FieldDef",
10
- "FieldInstance",
11
- "StructDef",
12
- "StructInstance",
13
- "decode",
14
- "encode",
15
- "encode_field",
16
- "load_enum_def_dict",
17
- "load_struct_def_dict",
18
- "save_enum_def_dict",
19
- "save_struct_def_dict",
20
- ]
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes