1
1
import enum as py_enum
2
2
import warnings
3
+ import operator
3
4
4
- from ..hdl .ast import Value , Shape , ShapeCastable , Const
5
+ from ..hdl .ast import Value , ValueCastable , Shape , ShapeCastable , Const
5
6
from ..hdl ._repr import *
6
7
7
8
8
- __all__ = py_enum .__all__
9
+ __all__ = py_enum .__all__ + [ "EnumView" , "FlagView" ]
9
10
10
11
11
12
for _member in py_enum .__all__ :
@@ -27,10 +28,10 @@ class EnumMeta(ShapeCastable, py_enum.EnumMeta):
27
28
28
29
# TODO: remove this shim once py3.8 support is dropped
29
30
@classmethod
30
- def __prepare__ (metacls , name , bases , shape = None , ** kwargs ):
31
+ def __prepare__ (metacls , name , bases , shape = None , view_class = None , ** kwargs ):
31
32
return super ().__prepare__ (name , bases , ** kwargs )
32
33
33
- def __new__ (metacls , name , bases , namespace , shape = None , ** kwargs ):
34
+ def __new__ (metacls , name , bases , namespace , shape = None , view_class = None , ** kwargs ):
34
35
if shape is not None :
35
36
shape = Shape .cast (shape )
36
37
# Prepare enumeration members for instantiation. This logic is unfortunately very
@@ -89,6 +90,8 @@ def __new__(metacls, name, bases, namespace, shape=None, **kwargs):
89
90
# Shape is provided explicitly. Set the `_amaranth_shape_` attribute, and check that
90
91
# the values of every member can be cast to the provided shape without truncation.
91
92
cls ._amaranth_shape_ = shape
93
+ if view_class is not None :
94
+ cls ._amaranth_view_class_ = view_class
92
95
else :
93
96
# Shape is not provided explicitly. Behave the same as a standard enumeration;
94
97
# the lack of `_amaranth_shape_` attribute is used to emit a warning when such
@@ -136,8 +139,12 @@ def __call__(cls, value, *args, **kwargs):
136
139
# At the moment however, for historical reasons, this is just the value itself. This works
137
140
# and is backwards-compatible but is limiting in that it does not allow us to e.g. catch
138
141
# comparisons with enum members of the wrong type.
139
- if isinstance (value , Value ):
140
- return value
142
+ if isinstance (value , (Value , ValueCastable )):
143
+ value = Value .cast (value )
144
+ if cls ._amaranth_view_class_ is None :
145
+ return value
146
+ else :
147
+ return cls ._amaranth_view_class_ (cls , value )
141
148
return super ().__call__ (value , * args , ** kwargs )
142
149
143
150
def const (cls , init ):
@@ -149,7 +156,7 @@ def const(cls, init):
149
156
member = cls (0 )
150
157
else :
151
158
member = cls (init )
152
- return Const (member .value , cls .as_shape ())
159
+ return cls ( Const (member .value , cls .as_shape () ))
153
160
154
161
def _value_repr (cls , value ):
155
162
yield Repr (FormatEnum (cls ), value )
@@ -174,9 +181,127 @@ class IntFlag(py_enum.IntFlag):
174
181
"""Subclass of the standard :class:`enum.IntFlag` that has :class:`EnumMeta` as
175
182
its metaclass."""
176
183
184
+
177
185
# Fix up the metaclass after the fact: the metaclass __new__ requires these classes
178
186
# to already be present, and also would not install itself on them due to lack of shape.
179
187
Enum .__class__ = EnumMeta
180
188
IntEnum .__class__ = EnumMeta
181
189
Flag .__class__ = EnumMeta
182
190
IntFlag .__class__ = EnumMeta
191
+
192
+
193
+ class EnumView (ValueCastable ):
194
+ def __init__ (self , enum , target ):
195
+ if not isinstance (enum , EnumMeta ) or not hasattr (enum , "_amaranth_shape_" ):
196
+ raise TypeError (f"EnumView type must be an enum with shape, not { enum !r} " )
197
+ try :
198
+ cast_target = Value .cast (target )
199
+ except TypeError as e :
200
+ raise TypeError ("EnumView target must be a value-castable object, not {!r}"
201
+ .format (target )) from e
202
+ if cast_target .shape () != enum .as_shape ():
203
+ raise TypeError ("EnumView target must have the same shape as the enum" )
204
+ self .enum = enum
205
+ self .target = cast_target
206
+
207
+ def shape (self ):
208
+ return self .enum
209
+
210
+ @ValueCastable .lowermethod
211
+ def as_value (self ):
212
+ return self .target
213
+
214
+ def eq (self , other ):
215
+ """Assign to the underlying value.
216
+
217
+ Returns
218
+ -------
219
+ :class:`Assign`
220
+ ``self.as_value().eq(other)``
221
+ """
222
+ return self .as_value ().eq (other )
223
+
224
+ def __add__ (self , other ):
225
+ raise TypeError ("cannot perform arithmetic operations on non-IntEnum enum" )
226
+
227
+ __radd__ = __add__
228
+ __sub__ = __add__
229
+ __rsub__ = __add__
230
+ __mul__ = __add__
231
+ __rmul__ = __add__
232
+ __floordiv__ = __add__
233
+ __rfloordiv__ = __add__
234
+ __mod__ = __add__
235
+ __rmod__ = __add__
236
+ __lshift__ = __add__
237
+ __rlshift__ = __add__
238
+ __rshift__ = __add__
239
+ __rrshift__ = __add__
240
+ __lt__ = __add__
241
+ __le__ = __add__
242
+ __gt__ = __add__
243
+ __ge__ = __add__
244
+
245
+ def __and__ (self , other ):
246
+ raise TypeError ("cannot perform bitwise operations on non-IntEnum non-Flag enum" )
247
+
248
+ __rand__ = __and__
249
+ __or__ = __and__
250
+ __ror__ = __and__
251
+ __xor__ = __and__
252
+ __rxor__ = __and__
253
+
254
+ def __eq__ (self , other ):
255
+ if isinstance (other , self .enum ):
256
+ other = self .enum (Value .cast (other ))
257
+ if not isinstance (other , EnumView ) or other .enum is not self .enum :
258
+ raise TypeError ("an EnumView can only be compared to value or other EnumView of the same enum type" )
259
+ return self .target == other .target
260
+
261
+ def __ne__ (self , other ):
262
+ if isinstance (other , self .enum ):
263
+ other = self .enum (Value .cast (other ))
264
+ if not isinstance (other , EnumView ) or other .enum is not self .enum :
265
+ raise TypeError ("an EnumView can only be compared to value or other EnumView of the same enum type" )
266
+ return self .target != other .target
267
+
268
+ def __repr__ (self ):
269
+ return f"{ type (self ).__name__ } ({ self .enum .__name__ } , { self .target !r} )"
270
+
271
+
272
+ class FlagView (EnumView ):
273
+ def __invert__ (self ):
274
+ if hasattr (self .enum , "_boundary_" ) and self .enum ._boundary_ in (EJECT , KEEP ):
275
+ return self .enum ._amaranth_view_class_ (self .enum , ~ self .target )
276
+ else :
277
+ singles_mask = 0
278
+ for flag in self .enum :
279
+ if (flag .value & (flag .value - 1 )) == 0 :
280
+ singles_mask |= flag .value
281
+ return self .enum ._amaranth_view_class_ (self .enum , ~ self .target & singles_mask )
282
+
283
+ def __bitop (self , other , op ):
284
+ if isinstance (other , self .enum ):
285
+ other = self .enum (Value .cast (other ))
286
+ if not isinstance (other , FlagView ) or other .enum is not self .enum :
287
+ raise TypeError ("a FlagView can only perform bitwise operation with a value or other FlagView of the same enum type" )
288
+ return self .enum ._amaranth_view_class_ (self .enum , op (self .target , other .target ))
289
+
290
+ def __and__ (self , other ):
291
+ return self .__bitop (other , operator .__and__ )
292
+
293
+ def __or__ (self , other ):
294
+ return self .__bitop (other , operator .__or__ )
295
+
296
+ def __xor__ (self , other ):
297
+ return self .__bitop (other , operator .__xor__ )
298
+
299
+ __rand__ = __and__
300
+ __ror__ = __or__
301
+ __rxor__ = __xor__
302
+
303
+
304
+ Enum ._amaranth_view_class_ = EnumView
305
+ IntEnum ._amaranth_view_class_ = None
306
+ Flag ._amaranth_view_class_ = FlagView
307
+ IntFlag ._amaranth_view_class_ = None
0 commit comments