Skip to content

Commit ef5c506

Browse files
wanda-phiwhitequark
authored andcommitted
lib.data: accept negative int and slice for ArrayLayout indexing.
As a side effect, makes iterating over `ArrayLayout` views work. Fixes #1405. Fixes #1418.
1 parent 6fe772d commit ef5c506

File tree

2 files changed

+76
-7
lines changed

2 files changed

+76
-7
lines changed

amaranth/lib/data.py

+25-6
Original file line numberDiff line numberDiff line change
@@ -796,17 +796,36 @@ def __getitem__(self, key):
796796
:exc:`TypeError`
797797
If :meth:`.ShapeCastable.__call__` does not return a value or a value-castable object.
798798
"""
799-
if isinstance(key, slice):
800-
raise TypeError(
801-
"View cannot be indexed with a slice; did you mean to call `.as_value()` first?")
802799
if isinstance(self.__layout, ArrayLayout):
803-
if not isinstance(key, (int, Value, ValueCastable)):
800+
elem_width = Shape.cast(self.__layout.elem_shape).width
801+
if isinstance(key, slice):
802+
start, stop, stride = key.indices(self.__layout.length)
803+
shape = ArrayLayout(self.__layout.elem_shape, len(range(start, stop, stride)))
804+
if stride == 1:
805+
value = self.__target[start * elem_width:stop * elem_width]
806+
else:
807+
value = Cat(self.__target[index * elem_width:(index + 1) * elem_width]
808+
for index in range(start, stop, stride))
809+
elif isinstance(key, int):
810+
if key not in range(-self.__layout.length, self.__layout.length):
811+
raise IndexError(f"Index {key} is out of range for array layout of length "
812+
f"{self.__layout.length}")
813+
if key < 0:
814+
key += self.__layout.length
815+
shape = self.__layout.elem_shape
816+
value = self.__target[key * elem_width:(key + 1) * elem_width]
817+
elif isinstance(key, (int, Value, ValueCastable)):
818+
shape = self.__layout.elem_shape
819+
value = self.__target.word_select(key, elem_width)
820+
else:
804821
raise TypeError(
805822
f"View with array layout may only be indexed with an integer or a value, "
806823
f"not {key!r}")
807-
shape = self.__layout.elem_shape
808-
value = self.__target.word_select(key, Shape.cast(self.__layout.elem_shape).width)
809824
else:
825+
if isinstance(key, slice):
826+
raise TypeError(
827+
"Non-array view cannot be indexed with a slice; did you mean to call "
828+
"`.as_value()` first?")
810829
if isinstance(key, (Value, ValueCastable)):
811830
raise TypeError(
812831
f"Only views with array layout, not {self.__layout!r}, may be indexed with "

tests/test_lib_data.py

+51-1
Original file line numberDiff line numberDiff line change
@@ -614,10 +614,52 @@ def test_getitem(self):
614614
self.assertEqual(v["q"].shape(), signed(1))
615615
self.assertRepr(v["r"][0], "(slice (slice (sig v) 0:4) 0:2)")
616616
self.assertRepr(v["r"][1], "(slice (slice (sig v) 0:4) 2:4)")
617+
self.assertRepr(v["r"][-2], "(slice (slice (sig v) 0:4) 0:2)")
618+
self.assertRepr(v["r"][-1], "(slice (slice (sig v) 0:4) 2:4)")
617619
self.assertRepr(v["r"][i], "(part (slice (sig v) 0:4) (sig i) 2 2)")
618620
self.assertRepr(v["t"][0]["u"], "(slice (slice (slice (sig v) 0:4) 0:2) 0:1)")
619621
self.assertRepr(v["t"][1]["v"], "(slice (slice (slice (sig v) 0:4) 2:4) 1:2)")
620622

623+
def test_getitem_slice(self):
624+
a = Signal(data.ArrayLayout(unsigned(2), 5))
625+
self.assertEqual(a[1:3].shape(), data.ArrayLayout(unsigned(2), 2))
626+
self.assertRepr(a[1:3].as_value(), "(slice (sig a) 2:6)")
627+
self.assertRepr(a[2:].as_value(), "(slice (sig a) 4:10)")
628+
self.assertRepr(a[:-2].as_value(), "(slice (sig a) 0:6)")
629+
self.assertRepr(a[-1:].as_value(), "(slice (sig a) 8:10)")
630+
self.assertRepr(a[::-1].as_value(), """
631+
(cat
632+
(slice (sig a) 8:10)
633+
(slice (sig a) 6:8)
634+
(slice (sig a) 4:6)
635+
(slice (sig a) 2:4)
636+
(slice (sig a) 0:2)
637+
)
638+
""")
639+
self.assertRepr(a[::2].as_value(), """
640+
(cat
641+
(slice (sig a) 0:2)
642+
(slice (sig a) 4:6)
643+
(slice (sig a) 8:10)
644+
)
645+
""")
646+
self.assertRepr(a[1::2].as_value(), """
647+
(cat
648+
(slice (sig a) 2:4)
649+
(slice (sig a) 6:8)
650+
)
651+
""")
652+
653+
def test_array_iter(self):
654+
a = Signal(data.ArrayLayout(unsigned(2), 5))
655+
l = list(a)
656+
self.assertEqual(len(l), 5)
657+
self.assertRepr(l[0], "(slice (sig a) 0:2)")
658+
self.assertRepr(l[1], "(slice (sig a) 2:4)")
659+
self.assertRepr(l[2], "(slice (sig a) 4:6)")
660+
self.assertRepr(l[3], "(slice (sig a) 6:8)")
661+
self.assertRepr(l[4], "(slice (sig a) 8:10)")
662+
621663
def test_getitem_custom_call(self):
622664
class Reverser(ShapeCastable):
623665
def as_shape(self):
@@ -674,9 +716,17 @@ def test_index_wrong_struct_dynamic(self):
674716
r"with a value$"):
675717
Signal(data.StructLayout({}))[Signal(1)]
676718

719+
def test_index_wrong_oob(self):
720+
with self.assertRaisesRegex(IndexError,
721+
r"^Index 2 is out of range for array layout of length 2$"):
722+
Signal(data.ArrayLayout(unsigned(2), 2))[2]
723+
with self.assertRaisesRegex(IndexError,
724+
r"^Index -3 is out of range for array layout of length 2$"):
725+
Signal(data.ArrayLayout(unsigned(2), 2))[-3]
726+
677727
def test_index_wrong_slice(self):
678728
with self.assertRaisesRegex(TypeError,
679-
r"^View cannot be indexed with a slice; did you mean to call `.as_value\(\)` "
729+
r"^Non-array view cannot be indexed with a slice; did you mean to call `.as_value\(\)` "
680730
r"first\?$"):
681731
Signal(data.StructLayout({}))[0:1]
682732

0 commit comments

Comments
 (0)