1
1
from abc import ABCMeta , abstractmethod
2
- import inspect
3
2
import warnings
4
3
import functools
5
4
import operator
12
11
from .. import tracer
13
12
from ..utils import *
14
13
from .._utils import *
15
- from .._utils import _ignore_deprecated
16
14
from .._unused import *
17
15
18
16
@@ -37,51 +35,23 @@ def __init__(self):
37
35
DUID .__next_uid += 1
38
36
39
37
40
- class ShapeCastable :
41
- """Interface of user-defined objects that can be cast to :class:`Shape` s.
42
-
43
- An object deriving from :class:`ShapeCastable` is automatically converted to a :class:`Shape`
44
- when it is used in a context where a :class:`Shape` is expected. Such objects can contain
45
- a richer description of the shape than what is supported by the core Amaranth language, yet
46
- still be transparently used with it.
47
- """
48
- def __init_subclass__ (cls , ** kwargs ):
49
- if not hasattr (cls , "as_shape" ):
50
- raise TypeError (f"Class '{ cls .__name__ } ' deriving from `ShapeCastable` must override "
51
- f"the `as_shape` method" )
52
- if not (hasattr (cls , "__call__" ) and inspect .isfunction (cls .__call__ )):
53
- raise TypeError (f"Class '{ cls .__name__ } ' deriving from `ShapeCastable` must override "
54
- f"the `__call__` method" )
55
- if not hasattr (cls , "const" ):
56
- raise TypeError (f"Class '{ cls .__name__ } ' deriving from `ShapeCastable` must override "
57
- f"the `const` method" )
58
-
59
- def _value_repr (self , value ):
60
- return (Repr (FormatInt (), value ),)
61
-
62
-
63
38
class Shape :
64
39
"""Bit width and signedness of a value.
65
40
66
- A ``Shape`` can be constructed using:
67
- * explicit bit width and signedness;
68
- * aliases :func:`signed` and :func:`unsigned`;
69
- * casting from a variety of objects.
41
+ A :class:`Shape` can be obtained by:
70
42
71
- A ``Shape`` can be cast from:
72
- * an integer, where the integer specifies the bit width;
73
- * a range, where the result is wide enough to represent any element of the range, and is
74
- signed if any element of the range is signed;
75
- * an :class:`Enum` with all integer members or :class:`IntEnum`, where the result is wide
76
- enough to represent any member of the enumeration, and is signed if any member of
77
- the enumeration is signed.
43
+ * constructing with explicit bit width and signedness;
44
+ * using the :func:`signed` and :func:`unsigned` aliases if the signedness is known upfront;
45
+ * casting from a variety of objects using the :meth:`cast` method.
78
46
79
47
Parameters
80
48
----------
81
49
width : int
82
- The number of bits in the representation, including the sign bit (if any).
50
+ The number of bits in the representation of a value. This includes the sign bit for signed
51
+ values. Cannot be zero if the value is signed.
83
52
signed : bool
84
- If ``False``, the value is unsigned. If ``True``, the value is signed two's complement.
53
+ Whether the value is signed. Signed values use the
54
+ `two's complement <https://en.wikipedia.org/wiki/Two's_complement>`_ representation.
85
55
"""
86
56
def __init__ (self , width = 1 , signed = False ):
87
57
if not isinstance (width , int ):
@@ -117,6 +87,27 @@ def _cast_plain_enum(obj):
117
87
118
88
@staticmethod
119
89
def cast (obj , * , src_loc_at = 0 ):
90
+ """Cast :pc:`obj` to a shape.
91
+
92
+ Many :ref:`shape-like <lang-shapelike>` objects can be cast to a shape:
93
+
94
+ * a :class:`Shape`, where the result is itself;
95
+ * an :class:`int`, where the result is :func:`unsigned(obj) <unsigned>`;
96
+ * a :class:`range`, where the result is wide enough to represent any element of the range,
97
+ and is signed if any element of the range is signed;
98
+ * an :class:`enum.Enum` whose members are all :ref:`constant-castable <lang-constcasting>`
99
+ or :class:`enum.IntEnum`, where the result is wide enough to represent any member of
100
+ the enumeration, and is signed if any member of the enumeration is signed;
101
+ * a :class:`ShapeCastable` object, where the result is obtained by repeatedly calling
102
+ :meth:`obj.as_shape() <ShapeCastable.as_shape>`.
103
+
104
+ Raises
105
+ ------
106
+ TypeError
107
+ If :pc:`obj` cannot be converted to a :class:`Shape`.
108
+ RecursionError
109
+ If :pc:`obj` is a :class:`ShapeCastable` object that casts to itself.
110
+ """
120
111
while True :
121
112
if isinstance (obj , Shape ):
122
113
return obj
@@ -142,6 +133,10 @@ def cast(obj, *, src_loc_at=0):
142
133
obj = new_obj
143
134
144
135
def __repr__ (self ):
136
+ """Python code that creates this shape.
137
+
138
+ Returns :pc:`f"signed({self.width})"` or :pc:`f"unsigned({self.width})"`.
139
+ """
145
140
if self .signed :
146
141
return f"signed({ self .width } )"
147
142
else :
@@ -152,6 +147,158 @@ def __eq__(self, other):
152
147
self .width == other .width and self .signed == other .signed )
153
148
154
149
150
+ def unsigned (width ):
151
+ """Returns :pc:`Shape(width, signed=False)`."""
152
+ return Shape (width , signed = False )
153
+
154
+
155
+ def signed (width ):
156
+ """Returns :pc:`Shape(width, signed=True)`."""
157
+ return Shape (width , signed = True )
158
+
159
+
160
+ class ShapeCastable :
161
+ """Interface class for objects that can be cast to a :class:`Shape`.
162
+
163
+ Shapes of values in the Amaranth language are specified using :ref:`shape-like objects
164
+ <lang-shapelike>`. Inheriting a class from :class:`ShapeCastable` and implementing all of
165
+ the methods described below adds instances of that class to the list of shape-like objects
166
+ recognized by the :meth:`Shape.cast` method. This is a part of the mechanism for seamlessly
167
+ extending the Amaranth language in third-party code.
168
+
169
+ To illustrate their purpose, consider constructing a signal from a shape-castable object
170
+ :pc:`shape_castable`:
171
+
172
+ .. code::
173
+
174
+ value_like = Signal(shape_castable, reset=initializer)
175
+
176
+ The code above is equivalent to:
177
+
178
+ .. code::
179
+
180
+ value_like = shape_castable(Signal(
181
+ shape_castable.as_shape(),
182
+ reset=shape_castable.const(initializer)
183
+ ))
184
+
185
+ Note that the :pc:`shape_castable(x)` syntax performs :pc:`shape_castable.__call__(x)`.
186
+
187
+ .. tip::
188
+
189
+ The source code of the :mod:`amaranth.lib.data` module can be used as a reference for
190
+ implementing a fully featured shape-castable object.
191
+ """
192
+ def __init_subclass__ (cls , ** kwargs ):
193
+ if cls .as_shape is ShapeCastable .as_shape :
194
+ raise TypeError (f"Class '{ cls .__name__ } ' deriving from 'ShapeCastable' must override "
195
+ f"the 'as_shape' method" )
196
+ if cls .const is ShapeCastable .const :
197
+ raise TypeError (f"Class '{ cls .__name__ } ' deriving from 'ShapeCastable' must override "
198
+ f"the 'const' method" )
199
+ if cls .__call__ is ShapeCastable .__call__ :
200
+ raise TypeError (f"Class '{ cls .__name__ } ' deriving from 'ShapeCastable' must override "
201
+ f"the '__call__' method" )
202
+
203
+ # The signatures and definitions of these methods are weird because they are present here for
204
+ # documentation (and error checking above) purpose only and should not affect control flow.
205
+ # This especially applies to `__call__`, where subclasses may call `super().__call__()` in
206
+ # creative ways.
207
+
208
+ def as_shape (self , * args , ** kwargs ):
209
+ """as_shape()
210
+
211
+ Convert :pc:`self` to a :ref:`shape-like object <lang-shapelike>`.
212
+
213
+ This method is called by the Amaranth language to convert :pc:`self` to a concrete
214
+ :class:`Shape`. It will usually return a :class:`Shape` object, but it may also return
215
+ another shape-like object to delegate its functionality.
216
+
217
+ This method must be idempotent: when called twice on the same object, the result must be
218
+ exactly the same.
219
+
220
+ This method may also be called by code that is not a part of the Amaranth language.
221
+
222
+ Returns
223
+ -------
224
+ Any other object recognized by :meth:`Shape.cast`.
225
+
226
+ Raises
227
+ ------
228
+ Exception
229
+ When the conversion cannot be done. This exception must be propagated by callers
230
+ (except when checking whether an object is shape-castable or not), either directly
231
+ or as a cause of another exception.
232
+ """
233
+ return super ().as_shape (* args , ** kwargs ) # :nocov:
234
+
235
+ def const (self , * args , ** kwargs ):
236
+ """const(obj)
237
+
238
+ Convert a constant initializer :pc:`obj` to its value representation.
239
+
240
+ This method is called by the Amaranth language to convert :pc:`obj`, which may be an
241
+ arbitrary Python object, to a concrete :ref:`value-like object <lang-valuelike>`.
242
+ The object :pc:`obj` will usually be a Python literal that can conveniently represent
243
+ a constant value whose shape is described by :pc:`self`. While not constrained here,
244
+ the result will usually be an instance of the return type of :meth:`__call__`.
245
+
246
+ For any :pc:`obj`, the following condition must hold:
247
+
248
+ .. code::
249
+
250
+ Shape.cast(self) == Const.cast(self.const(obj)).shape()
251
+
252
+ This method may also be called by code that is not a part of the Amaranth language.
253
+
254
+ Returns
255
+ -------
256
+ A :ref:`value-like object <lang-valuelike>` that is :ref:`constant-castable <lang-constcasting>`.
257
+
258
+ Raises
259
+ ------
260
+ Exception
261
+ When the conversion cannot be done. This exception must be propagated by callers,
262
+ either directly or as a cause of another exception. While not constrained here,
263
+ usually the exception class will be :exc:`TypeError` or :exc:`ValueError`.
264
+ """
265
+ return super ().const (* args , ** kwargs ) # :nocov:
266
+
267
+ def __call__ (self , * args , ** kwargs ):
268
+ """__call__(obj)
269
+
270
+ Lift a :ref:`value-like object <lang-valuelike>` to a higher-level representation.
271
+
272
+ This method is called by the Amaranth language to lift :pc:`obj`, which may be any
273
+ :ref:`value-like object <lang-valuelike>` whose shape equals :pc:`Shape.cast(self)`,
274
+ to a higher-level representation, which may be any value-like object with the same
275
+ shape. While not constrained here, usually a :class:`ShapeCastable` implementation will
276
+ be paired with a :class:`ValueCastable` implementation, and this method will return
277
+ an instance of the latter.
278
+
279
+ If :pc:`obj` is not as described above, this interface does not constrain the behavior
280
+ of this method. This may be used to implement another call-based protocol at the same
281
+ time.
282
+
283
+ For any compliant :pc:`obj`, the following condition must hold:
284
+
285
+ .. code::
286
+
287
+ Value.cast(self(obj)) == Value.cast(obj)
288
+
289
+ This method may also be called by code that is not a part of the Amaranth language.
290
+
291
+ Returns
292
+ -------
293
+ A :ref:`value-like object <lang-valuelike>`.
294
+ """
295
+ return super ().__call__ (* args , ** kwargs ) # :nocov:
296
+
297
+ # TODO: write an RFC for turning this into a proper interface method
298
+ def _value_repr (self , value ):
299
+ return (Repr (FormatInt (), value ),)
300
+
301
+
155
302
class _ShapeLikeMeta (type ):
156
303
def __subclasscheck__ (cls , subclass ):
157
304
return issubclass (subclass , (Shape , ShapeCastable , int , range , EnumMeta )) or subclass is ShapeLike
@@ -173,39 +320,28 @@ def __instancecheck__(cls, instance):
173
320
class ShapeLike (metaclass = _ShapeLikeMeta ):
174
321
"""An abstract class representing all objects that can be cast to a :class:`Shape`.
175
322
176
- `` issubclass(cls, ShapeLike)`` returns `` True` ` for:
323
+ :pc:` issubclass(cls, ShapeLike)` returns :pc:` True` for:
177
324
178
- - :class:`Shape`
179
- - :class:`ShapeCastable` and its subclasses
180
- - `` int`` and its subclasses
181
- - `` range`` and its subclasses
182
- - :class:`enum.EnumMeta` and its subclasses
183
- - :class:`ShapeLike` itself
325
+ * :class:`Shape`;
326
+ * :class:`ShapeCastable` and its subclasses;
327
+ * :class:` int` and its subclasses;
328
+ * :class:` range` and its subclasses;
329
+ * :class:`enum.EnumMeta` and its subclasses;
330
+ * :class:`ShapeLike` itself.
184
331
185
- `` isinstance(obj, ShapeLike)`` returns `` True` ` for:
332
+ :pc:` isinstance(obj, ShapeLike)` returns :pc:` True` for:
186
333
187
- - :class:`Shape` instances
188
- - :class:`ShapeCastable` instances
189
- - non-negative `` int`` values
190
- - `` range`` instances
191
- - :class:`enum.Enum` subclasses where all values are :ref:`value-like <lang-valuelike>`
334
+ * :class:`Shape` instances;
335
+ * :class:`ShapeCastable` instances;
336
+ * non-negative :class:` int` values;
337
+ * :class:` range` instances;
338
+ * :class:`enum.Enum` subclasses where all values are :ref:`value-like objects <lang-valuelike>`.
192
339
193
- This class is only usable for the above checks — no instances and no (non-virtual)
194
- subclasses can be created .
340
+ This class cannot be instantiated or subclassed. It can only be used for checking types of
341
+ objects .
195
342
"""
196
-
197
343
def __new__ (cls , * args , ** kwargs ):
198
- raise TypeError ("ShapeLike is an abstract class and cannot be constructed" )
199
-
200
-
201
- def unsigned (width ):
202
- """Shorthand for ``Shape(width, signed=False)``."""
203
- return Shape (width , signed = False )
204
-
205
-
206
- def signed (width ):
207
- """Shorthand for ``Shape(width, signed=True)``."""
208
- return Shape (width , signed = True )
344
+ raise TypeError ("ShapeLike is an abstract class and cannot be instantiated" )
209
345
210
346
211
347
def _overridable_by_reflected (method_name ):
0 commit comments