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