Skip to content

Commit 726f12d

Browse files
committed
feat: implement the proposal in issue #5
1 parent fffff7f commit 726f12d

5 files changed

Lines changed: 141 additions & 1 deletion

File tree

libdestruct/common/array/array.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,20 @@
77
from __future__ import annotations
88

99
from abc import abstractmethod
10+
from types import GenericAlias
1011

1112
from libdestruct.common.obj import obj
1213

1314

1415
class array(obj):
1516
"""An array of objects."""
1617

18+
def __class_getitem__(cls, params: tuple) -> GenericAlias:
19+
"""Support array[c_int, 3] subscript syntax."""
20+
if not isinstance(params, tuple):
21+
params = (params,)
22+
return GenericAlias(cls, params)
23+
1724
@abstractmethod
1825
def count(self: array) -> int:
1926
"""Return the size of the array."""

libdestruct/common/array/array_field_inflater.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
from typing import TYPE_CHECKING
1111

12+
from libdestruct.common.array.array import array
1213
from libdestruct.common.array.linear_array_field import LinearArrayField
1314
from libdestruct.common.type_registry import TypeRegistry
1415

@@ -18,6 +19,7 @@
1819
from libdestruct.backing.resolver import Resolver
1920
from libdestruct.common.obj import obj
2021

22+
2123
registry = TypeRegistry()
2224

2325

@@ -32,4 +34,21 @@ def linear_array_field_inflater(
3234
return field.inflate
3335

3436

37+
def _subscripted_array_handler(
38+
item: object,
39+
args: tuple,
40+
owner: tuple[obj, type[obj]] | None,
41+
) -> Callable[[Resolver], obj] | None:
42+
"""Handle subscripted array types like array[c_int, 3]."""
43+
if len(args) != 2:
44+
return None
45+
element_type, count = args
46+
if not isinstance(count, int):
47+
return None
48+
field = LinearArrayField(element_type, count)
49+
field.item = registry.inflater_for(element_type)
50+
return field.inflate
51+
52+
3553
registry.register_instance_handler(LinearArrayField, linear_array_field_inflater)
54+
registry.register_generic_handler(array, _subscripted_array_handler)

libdestruct/common/enum/enum.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
from __future__ import annotations
88

9+
from types import GenericAlias
910
from typing import TYPE_CHECKING
1011

1112
from libdestruct.common.obj import obj
@@ -20,6 +21,12 @@
2021
class enum(obj):
2122
"""A generic enum."""
2223

24+
def __class_getitem__(cls, params: tuple) -> GenericAlias:
25+
"""Support enum[MyEnum] and enum[MyEnum, c_short] subscript syntax."""
26+
if not isinstance(params, tuple):
27+
params = (params,)
28+
return GenericAlias(cls, params)
29+
2330
python_enum: type[Enum]
2431
"""The backing Python enum."""
2532

libdestruct/common/enum/enum_field_inflater.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88

99
from typing import TYPE_CHECKING
1010

11+
from libdestruct.c.c_integer_types import c_int
12+
from libdestruct.common.enum.enum import enum
1113
from libdestruct.common.enum.int_enum_field import IntEnumField
1214
from libdestruct.common.type_registry import TypeRegistry
1315

@@ -30,4 +32,19 @@ def generic_enum_field_inflater(
3032
return field.inflate
3133

3234

35+
def _subscripted_enum_handler(
36+
item: object,
37+
args: tuple,
38+
owner: tuple[obj, type[obj]] | None,
39+
) -> Callable[[Resolver], obj] | None:
40+
"""Handle subscripted enum types like enum[MyEnum] or enum[MyEnum, c_short]."""
41+
if not args:
42+
return None
43+
python_enum = args[0]
44+
backing_type = args[1] if len(args) > 1 else c_int
45+
field = IntEnumField(python_enum, size=backing_type.size)
46+
return field.inflate
47+
48+
3349
registry.register_instance_handler(IntEnumField, generic_enum_field_inflater)
50+
registry.register_generic_handler(enum, _subscripted_enum_handler)

test/scripts/struct_unit_test.py

Lines changed: 91 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
import unittest
88
from enum import IntEnum
99

10-
from libdestruct import c_int, c_long, c_uint, inflater, struct, ptr, ptr_to_self, array_of, enum, enum_of
10+
from libdestruct import array, c_int, c_long, c_short, c_uint, inflater, struct, ptr, ptr_to_self, array_of, enum, enum_of
1111

1212

1313
class StructMemberCollisionTest(unittest.TestCase):
@@ -264,6 +264,96 @@ class TreeNode(struct):
264264
self.assertEqual(node.data.value, 42)
265265

266266

267+
class SubscriptSyntaxTest(unittest.TestCase):
268+
"""Test the subscript syntax: enum[T], array[T, N], ptr[T]."""
269+
270+
def test_enum_subscript(self):
271+
"""enum[MyEnum] works as a type annotation for struct fields."""
272+
class Color(IntEnum):
273+
RED = 0
274+
GREEN = 1
275+
BLUE = 2
276+
277+
class s_t(struct):
278+
color: enum[Color]
279+
280+
memory = (1).to_bytes(4, "little")
281+
s = s_t.from_bytes(memory)
282+
self.assertEqual(s.color.value, Color.GREEN)
283+
284+
def test_enum_subscript_custom_backing(self):
285+
"""enum[MyEnum, c_short] uses a custom backing type."""
286+
class Status(IntEnum):
287+
OFF = 0
288+
ON = 1
289+
290+
class s_t(struct):
291+
status: enum[Status, c_short]
292+
293+
memory = (1).to_bytes(2, "little")
294+
s = s_t.from_bytes(memory)
295+
self.assertEqual(s.status.value, Status.ON)
296+
from libdestruct import size_of
297+
self.assertEqual(size_of(s_t), 2)
298+
299+
def test_array_subscript(self):
300+
"""array[c_int, 3] works as a type annotation for struct fields."""
301+
class s_t(struct):
302+
data: array[c_int, 3]
303+
304+
memory = b""
305+
for v in [10, 20, 30]:
306+
memory += v.to_bytes(4, "little")
307+
308+
s = s_t.from_bytes(memory)
309+
self.assertEqual(s.data[0].value, 10)
310+
self.assertEqual(s.data[1].value, 20)
311+
self.assertEqual(s.data[2].value, 30)
312+
313+
def test_array_subscript_size(self):
314+
"""array[c_int, 3] has correct size."""
315+
class s_t(struct):
316+
data: array[c_int, 3]
317+
318+
from libdestruct import size_of
319+
self.assertEqual(size_of(s_t), 12)
320+
321+
def test_ptr_subscript(self):
322+
"""ptr[T] works as a type annotation (already supported)."""
323+
class s_t(struct):
324+
val: c_int
325+
ref: ptr[c_int]
326+
327+
memory = b""
328+
memory += (42).to_bytes(4, "little")
329+
memory += (0).to_bytes(8, "little")
330+
331+
s = s_t.from_bytes(memory)
332+
self.assertEqual(s.val.value, 42)
333+
334+
def test_mixed_subscript_struct(self):
335+
"""Struct mixing all subscript syntaxes."""
336+
class Direction(IntEnum):
337+
UP = 0
338+
DOWN = 1
339+
340+
class s_t(struct):
341+
dir: enum[Direction]
342+
coords: array[c_int, 2]
343+
next: ptr["s_t"]
344+
345+
memory = b""
346+
memory += (1).to_bytes(4, "little") # dir = DOWN
347+
memory += (10).to_bytes(4, "little") # coords[0]
348+
memory += (20).to_bytes(4, "little") # coords[1]
349+
memory += (0).to_bytes(8, "little") # next = null
350+
351+
s = s_t.from_bytes(memory)
352+
self.assertEqual(s.dir.value, Direction.DOWN)
353+
self.assertEqual(s.coords[0].value, 10)
354+
self.assertEqual(s.coords[1].value, 20)
355+
356+
267357
class StructEqualityTest(unittest.TestCase):
268358
def test_struct_eq_non_struct_returns_not_implemented(self):
269359
"""struct.__eq__ returns NotImplemented for non-struct values."""

0 commit comments

Comments
 (0)