Implement RFC 58: Core support for ValueCastable
formatting.
This commit is contained in:
parent
f21d3d0c6a
commit
0e6d802de4
|
@ -385,6 +385,27 @@ class ShapeCastable:
|
||||||
"""
|
"""
|
||||||
return super().__call__(*args, **kwargs) # :nocov:
|
return super().__call__(*args, **kwargs) # :nocov:
|
||||||
|
|
||||||
|
def format(self, obj, spec):
|
||||||
|
"""Format a value.
|
||||||
|
|
||||||
|
This method is called by the Amaranth language to implement formatting for custom
|
||||||
|
shapes. Whenever :py:`"{obj:spec}"` is encountered by :class:`Format`, and :py:`obj`
|
||||||
|
has a custom shape that has a :meth:`format` method, :py:`obj.shape().format(obj, "spec")`
|
||||||
|
is called, and the format specifier is replaced with the result.
|
||||||
|
|
||||||
|
The default :meth:`format` implementation is:
|
||||||
|
|
||||||
|
.. code::
|
||||||
|
|
||||||
|
def format(self, obj, spec):
|
||||||
|
return Format(f"{{:{spec}}}", Value.cast(obj))
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
:class:`Format`
|
||||||
|
"""
|
||||||
|
return Format(f"{{:{spec}}}", Value.cast(obj))
|
||||||
|
|
||||||
# TODO: write an RFC for turning this into a proper interface method
|
# TODO: write an RFC for turning this into a proper interface method
|
||||||
def _value_repr(self, value):
|
def _value_repr(self, value):
|
||||||
return (_repr.Repr(_repr.FormatInt(), value),)
|
return (_repr.Repr(_repr.FormatInt(), value),)
|
||||||
|
@ -2576,6 +2597,9 @@ class Format:
|
||||||
chunks.append(literal)
|
chunks.append(literal)
|
||||||
if field_name is not None:
|
if field_name is not None:
|
||||||
obj = get_field(field_name)
|
obj = get_field(field_name)
|
||||||
|
if conversion == "v":
|
||||||
|
obj = Value.cast(obj)
|
||||||
|
else:
|
||||||
obj = fmt.convert_field(obj, conversion)
|
obj = fmt.convert_field(obj, conversion)
|
||||||
format_spec = subformat(format_spec)
|
format_spec = subformat(format_spec)
|
||||||
if isinstance(obj, Value):
|
if isinstance(obj, Value):
|
||||||
|
@ -2583,7 +2607,16 @@ class Format:
|
||||||
self._parse_format_spec(format_spec, obj.shape())
|
self._parse_format_spec(format_spec, obj.shape())
|
||||||
chunks.append((obj, format_spec))
|
chunks.append((obj, format_spec))
|
||||||
elif isinstance(obj, ValueCastable):
|
elif isinstance(obj, ValueCastable):
|
||||||
raise TypeError("'ValueCastable' formatting is not supported")
|
shape = obj.shape()
|
||||||
|
if isinstance(shape, ShapeCastable):
|
||||||
|
fmt = shape.format(obj, format_spec)
|
||||||
|
if not isinstance(fmt, Format):
|
||||||
|
raise TypeError(f"`ShapeCastable.format` must return a 'Format' instance, not {fmt!r}")
|
||||||
|
chunks += fmt._chunks
|
||||||
|
else:
|
||||||
|
obj = Value.cast(obj)
|
||||||
|
self._parse_format_spec(format_spec, obj.shape())
|
||||||
|
chunks.append((obj, format_spec))
|
||||||
elif isinstance(obj, Format):
|
elif isinstance(obj, Format):
|
||||||
if format_spec != "":
|
if format_spec != "":
|
||||||
raise ValueError(f"Format specifiers ({format_spec!r}) cannot be used for 'Format' objects")
|
raise ValueError(f"Format specifiers ({format_spec!r}) cannot be used for 'Format' objects")
|
||||||
|
|
|
@ -56,6 +56,7 @@ Implemented RFCs
|
||||||
.. _RFC 51: https://amaranth-lang.org/rfcs/0051-const-from-bits.html
|
.. _RFC 51: https://amaranth-lang.org/rfcs/0051-const-from-bits.html
|
||||||
.. _RFC 53: https://amaranth-lang.org/rfcs/0053-ioport.html
|
.. _RFC 53: https://amaranth-lang.org/rfcs/0053-ioport.html
|
||||||
.. _RFC 55: https://amaranth-lang.org/rfcs/0055-lib-io.html
|
.. _RFC 55: https://amaranth-lang.org/rfcs/0055-lib-io.html
|
||||||
|
.. _RFC 58: https://amaranth-lang.org/rfcs/0058-valuecastable-format.html
|
||||||
.. _RFC 59: https://amaranth-lang.org/rfcs/0059-no-domain-upwards-propagation.html
|
.. _RFC 59: https://amaranth-lang.org/rfcs/0059-no-domain-upwards-propagation.html
|
||||||
.. _RFC 62: https://amaranth-lang.org/rfcs/0062-memory-data.html
|
.. _RFC 62: https://amaranth-lang.org/rfcs/0062-memory-data.html
|
||||||
|
|
||||||
|
@ -68,6 +69,7 @@ Implemented RFCs
|
||||||
* `RFC 50`_: ``Print`` statement and string formatting
|
* `RFC 50`_: ``Print`` statement and string formatting
|
||||||
* `RFC 51`_: Add ``ShapeCastable.from_bits`` and ``amaranth.lib.data.Const``
|
* `RFC 51`_: Add ``ShapeCastable.from_bits`` and ``amaranth.lib.data.Const``
|
||||||
* `RFC 53`_: Low-level I/O primitives
|
* `RFC 53`_: Low-level I/O primitives
|
||||||
|
* `RFC 58`_: Core support for ``ValueCastable`` formatting
|
||||||
* `RFC 59`_: Get rid of upwards propagation of clock domains
|
* `RFC 59`_: Get rid of upwards propagation of clock domains
|
||||||
* `RFC 62`_: The `MemoryData`` class
|
* `RFC 62`_: The `MemoryData`` class
|
||||||
|
|
||||||
|
|
|
@ -1412,6 +1412,79 @@ class MockValueCastable(ValueCastable):
|
||||||
return self.dest
|
return self.dest
|
||||||
|
|
||||||
|
|
||||||
|
class MockShapeCastableFormat(ShapeCastable):
|
||||||
|
def __init__(self, dest):
|
||||||
|
self.dest = dest
|
||||||
|
|
||||||
|
def as_shape(self):
|
||||||
|
return self.dest
|
||||||
|
|
||||||
|
def __call__(self, value):
|
||||||
|
return value
|
||||||
|
|
||||||
|
def const(self, init):
|
||||||
|
return Const(init, self.dest)
|
||||||
|
|
||||||
|
def from_bits(self, bits):
|
||||||
|
return bits
|
||||||
|
|
||||||
|
def format(self, value, format_desc):
|
||||||
|
return Format("_{}_{}_", Value.cast(value), format_desc)
|
||||||
|
|
||||||
|
|
||||||
|
class MockValueCastableFormat(ValueCastable):
|
||||||
|
def __init__(self, dest):
|
||||||
|
self.dest = dest
|
||||||
|
|
||||||
|
def shape(self):
|
||||||
|
return MockShapeCastableFormat(Value.cast(self.dest).shape())
|
||||||
|
|
||||||
|
def as_value(self):
|
||||||
|
return self.dest
|
||||||
|
|
||||||
|
|
||||||
|
class MockValueCastableNoFormat(ValueCastable):
|
||||||
|
def __init__(self, dest):
|
||||||
|
self.dest = dest
|
||||||
|
|
||||||
|
def shape(self):
|
||||||
|
return MockShapeCastable(Value.cast(self.dest).shape())
|
||||||
|
|
||||||
|
def as_value(self):
|
||||||
|
return self.dest
|
||||||
|
|
||||||
|
|
||||||
|
class MockShapeCastableFormatWrong(ShapeCastable):
|
||||||
|
def __init__(self, dest):
|
||||||
|
self.dest = dest
|
||||||
|
|
||||||
|
def as_shape(self):
|
||||||
|
return self.dest
|
||||||
|
|
||||||
|
def __call__(self, value):
|
||||||
|
return value
|
||||||
|
|
||||||
|
def const(self, init):
|
||||||
|
return Const(init, self.dest)
|
||||||
|
|
||||||
|
def from_bits(self, bits):
|
||||||
|
return bits
|
||||||
|
|
||||||
|
def format(self, value, format_desc):
|
||||||
|
return Value.cast(value)
|
||||||
|
|
||||||
|
|
||||||
|
class MockValueCastableFormatWrong(ValueCastable):
|
||||||
|
def __init__(self, dest):
|
||||||
|
self.dest = dest
|
||||||
|
|
||||||
|
def shape(self):
|
||||||
|
return MockShapeCastableFormatWrong(Value.cast(self.dest).shape())
|
||||||
|
|
||||||
|
def as_value(self):
|
||||||
|
return self.dest
|
||||||
|
|
||||||
|
|
||||||
class MockValueCastableChanges(ValueCastable):
|
class MockValueCastableChanges(ValueCastable):
|
||||||
def __init__(self, width=0):
|
def __init__(self, width=0):
|
||||||
self.width = width
|
self.width = width
|
||||||
|
@ -1567,6 +1640,21 @@ class FormatTestCase(FHDLTestCase):
|
||||||
fmt = Format("sub: {}, c: {:4x}", subfmt, c)
|
fmt = Format("sub: {}, c: {:4x}", subfmt, c)
|
||||||
self.assertRepr(fmt, "(format 'sub: a: {:2x}, b: {:3x}, c: {:4x}' (sig a) (sig b) (sig c))")
|
self.assertRepr(fmt, "(format 'sub: a: {:2x}, b: {:3x}, c: {:4x}' (sig a) (sig b) (sig c))")
|
||||||
|
|
||||||
|
def test_construct_valuecastable(self):
|
||||||
|
a = Signal()
|
||||||
|
b = MockValueCastable(a)
|
||||||
|
fmt = Format("{:x}", b)
|
||||||
|
self.assertRepr(fmt, "(format '{:x}' (sig a))")
|
||||||
|
c = MockValueCastableFormat(a)
|
||||||
|
fmt = Format("{:meow}", c)
|
||||||
|
self.assertRepr(fmt, "(format '_{}_meow_' (sig a))")
|
||||||
|
d = MockValueCastableNoFormat(a)
|
||||||
|
fmt = Format("{:x}", d)
|
||||||
|
self.assertRepr(fmt, "(format '{:x}' (sig a))")
|
||||||
|
e = MockValueCastableFormat(a)
|
||||||
|
fmt = Format("{!v:x}", e)
|
||||||
|
self.assertRepr(fmt, "(format '{:x}' (sig a))")
|
||||||
|
|
||||||
def test_construct_wrong(self):
|
def test_construct_wrong(self):
|
||||||
a = Signal()
|
a = Signal()
|
||||||
b = Signal(signed(16))
|
b = Signal(signed(16))
|
||||||
|
@ -1576,9 +1664,6 @@ class FormatTestCase(FHDLTestCase):
|
||||||
with self.assertRaisesRegex(ValueError,
|
with self.assertRaisesRegex(ValueError,
|
||||||
r"^cannot switch from automatic field numbering to manual field specification$"):
|
r"^cannot switch from automatic field numbering to manual field specification$"):
|
||||||
Format("{}, {1}", a, b)
|
Format("{}, {1}", a, b)
|
||||||
with self.assertRaisesRegex(TypeError,
|
|
||||||
r"^'ValueCastable' formatting is not supported$"):
|
|
||||||
Format("{}", MockValueCastable(Const(0)))
|
|
||||||
with self.assertRaisesRegex(ValueError,
|
with self.assertRaisesRegex(ValueError,
|
||||||
r"^Format specifiers \('s'\) cannot be used for 'Format' objects$"):
|
r"^Format specifiers \('s'\) cannot be used for 'Format' objects$"):
|
||||||
Format("{:s}", Format(""))
|
Format("{:s}", Format(""))
|
||||||
|
@ -1622,6 +1707,14 @@ class FormatTestCase(FHDLTestCase):
|
||||||
r"^Cannot specify '_' with format specifier 'c'$"):
|
r"^Cannot specify '_' with format specifier 'c'$"):
|
||||||
Format("{a:_c}", a=a)
|
Format("{a:_c}", a=a)
|
||||||
|
|
||||||
|
def test_construct_valuecastable_wrong(self):
|
||||||
|
a = Signal()
|
||||||
|
b = MockValueCastableFormatWrong(a)
|
||||||
|
with self.assertRaisesRegex(TypeError,
|
||||||
|
r"^`ShapeCastable.format` must return a 'Format' instance, "
|
||||||
|
r"not \(sig a\)$"):
|
||||||
|
fmt = Format("{:x}", b)
|
||||||
|
|
||||||
def test_plus(self):
|
def test_plus(self):
|
||||||
a = Signal()
|
a = Signal()
|
||||||
b = Signal()
|
b = Signal()
|
||||||
|
|
Loading…
Reference in a new issue