hdl._ast: add Format.Enum
, Format.Struct
, Format.Array
.
This commit is contained in:
parent
6f5d009fad
commit
3c870d6b73
|
@ -2559,8 +2559,27 @@ class Initial(Value):
|
||||||
return "(initial)"
|
return "(initial)"
|
||||||
|
|
||||||
|
|
||||||
|
class _FormatLike:
|
||||||
|
def _as_format(self) -> "Format":
|
||||||
|
raise NotImplementedError # :nocov:
|
||||||
|
|
||||||
|
def __add__(self, other):
|
||||||
|
if not isinstance(other, _FormatLike):
|
||||||
|
return NotImplemented
|
||||||
|
return Format._from_chunks(self._as_format()._chunks + other._as_format()._chunks)
|
||||||
|
|
||||||
|
def __format__(self, format_desc):
|
||||||
|
"""Forbidden formatting.
|
||||||
|
|
||||||
|
``Format`` objects cannot be directly formatted for the same reason as the ``Value``s
|
||||||
|
they contain.
|
||||||
|
"""
|
||||||
|
raise TypeError(f"Format object {self!r} cannot be converted to string. Use `repr` "
|
||||||
|
f"to print the AST, or pass it to the `Print` statement.")
|
||||||
|
|
||||||
|
|
||||||
@final
|
@final
|
||||||
class Format:
|
class Format(_FormatLike):
|
||||||
def __init__(self, format, *args, **kwargs):
|
def __init__(self, format, *args, **kwargs):
|
||||||
fmt = string.Formatter()
|
fmt = string.Formatter()
|
||||||
chunks = []
|
chunks = []
|
||||||
|
@ -2615,17 +2634,17 @@ class Format:
|
||||||
shape = obj.shape()
|
shape = obj.shape()
|
||||||
if isinstance(shape, ShapeCastable):
|
if isinstance(shape, ShapeCastable):
|
||||||
fmt = shape.format(obj, format_spec)
|
fmt = shape.format(obj, format_spec)
|
||||||
if not isinstance(fmt, Format):
|
if not isinstance(fmt, _FormatLike):
|
||||||
raise TypeError(f"`ShapeCastable.format` must return a 'Format' instance, not {fmt!r}")
|
raise TypeError(f"`ShapeCastable.format` must return a 'Format' instance, not {fmt!r}")
|
||||||
chunks += fmt._chunks
|
chunks += fmt._as_format()._chunks
|
||||||
else:
|
else:
|
||||||
obj = Value.cast(obj)
|
obj = Value.cast(obj)
|
||||||
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, Format):
|
elif isinstance(obj, _FormatLike):
|
||||||
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")
|
||||||
chunks += obj._chunks
|
chunks += obj._as_format()._chunks
|
||||||
else:
|
else:
|
||||||
chunks.append(fmt.format_field(obj, format_spec))
|
chunks.append(fmt.format_field(obj, format_spec))
|
||||||
|
|
||||||
|
@ -2638,6 +2657,9 @@ class Format:
|
||||||
|
|
||||||
self._chunks = self._clean_chunks(chunks)
|
self._chunks = self._clean_chunks(chunks)
|
||||||
|
|
||||||
|
def _as_format(self):
|
||||||
|
return self
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _from_chunks(cls, chunks):
|
def _from_chunks(cls, chunks):
|
||||||
res = object.__new__(cls)
|
res = object.__new__(cls)
|
||||||
|
@ -2671,25 +2693,11 @@ class Format:
|
||||||
format_string.append("{}")
|
format_string.append("{}")
|
||||||
return ("".join(format_string), tuple(args))
|
return ("".join(format_string), tuple(args))
|
||||||
|
|
||||||
def __add__(self, other):
|
|
||||||
if not isinstance(other, Format):
|
|
||||||
return NotImplemented
|
|
||||||
return Format._from_chunks(self._chunks + other._chunks)
|
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
format_string, args = self._to_format_string()
|
format_string, args = self._to_format_string()
|
||||||
args = "".join(f" {arg!r}" for arg in args)
|
args = "".join(f" {arg!r}" for arg in args)
|
||||||
return f"(format {format_string!r}{args})"
|
return f"(format {format_string!r}{args})"
|
||||||
|
|
||||||
def __format__(self, format_desc):
|
|
||||||
"""Forbidden formatting.
|
|
||||||
|
|
||||||
``Format`` objects cannot be directly formatted for the same reason as the ``Value``s
|
|
||||||
they contain.
|
|
||||||
"""
|
|
||||||
raise TypeError(f"Format object {self!r} cannot be converted to string. Use `repr` "
|
|
||||||
f"to print the AST, or pass it to the `Print` statement.")
|
|
||||||
|
|
||||||
_FORMAT_SPEC_PATTERN = re.compile(r"""
|
_FORMAT_SPEC_PATTERN = re.compile(r"""
|
||||||
(?:
|
(?:
|
||||||
(?P<fill>.)?
|
(?P<fill>.)?
|
||||||
|
@ -2760,6 +2768,90 @@ class Format:
|
||||||
return res
|
return res
|
||||||
|
|
||||||
|
|
||||||
|
class Enum(_FormatLike):
|
||||||
|
def __init__(self, value, /, variants):
|
||||||
|
self._value = Value.cast(value)
|
||||||
|
if isinstance(variants, EnumMeta):
|
||||||
|
self._variants = {Const.cast(member.value).value: member.name for member in variants}
|
||||||
|
else:
|
||||||
|
self._variants = dict(variants)
|
||||||
|
for val, name in self._variants.items():
|
||||||
|
if not isinstance(val, int):
|
||||||
|
raise TypeError(f"Variant values must be integers, not {val!r}")
|
||||||
|
if not isinstance(name, str):
|
||||||
|
raise TypeError(f"Variant names must be strings, not {name!r}")
|
||||||
|
|
||||||
|
def _as_format(self):
|
||||||
|
def str_val(name):
|
||||||
|
name = name.encode()
|
||||||
|
return Const(int.from_bytes(name, "little"), len(name) * 8)
|
||||||
|
value = SwitchValue(self._value, [
|
||||||
|
(val, str_val(name))
|
||||||
|
for val, name in self._variants.items()
|
||||||
|
] + [(None, str_val("[unknown]"))])
|
||||||
|
return Format("{:s}", value)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
variants = "".join(
|
||||||
|
f" ({val!r} {name!r})"
|
||||||
|
for val, name in self._variants.items()
|
||||||
|
)
|
||||||
|
return f"(format-enum {self._value!r}{variants})"
|
||||||
|
|
||||||
|
|
||||||
|
class Struct(_FormatLike):
|
||||||
|
def __init__(self, value, /, fields):
|
||||||
|
self._value = Value.cast(value)
|
||||||
|
self._fields: dict[str, _FormatLike] = dict(fields)
|
||||||
|
for name, format in self._fields.items():
|
||||||
|
if not isinstance(name, str):
|
||||||
|
raise TypeError(f"Field names must be strings, not {name!r}")
|
||||||
|
if not isinstance(format, _FormatLike):
|
||||||
|
raise TypeError(f"Field format must be a 'Format', not {format!r}")
|
||||||
|
|
||||||
|
def _as_format(self):
|
||||||
|
chunks = ["{"]
|
||||||
|
for idx, (name, field) in enumerate(self._fields.items()):
|
||||||
|
if idx != 0:
|
||||||
|
chunks.append(", ")
|
||||||
|
chunks.append(f"{name}=")
|
||||||
|
chunks += field._as_format()._chunks
|
||||||
|
chunks.append("}")
|
||||||
|
return Format._from_chunks(chunks)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
fields = "".join(
|
||||||
|
f" ({name!r} {field!r})"
|
||||||
|
for name, field in self._fields.items()
|
||||||
|
)
|
||||||
|
return f"(format-struct {self._value!r}{fields})"
|
||||||
|
|
||||||
|
|
||||||
|
class Array(_FormatLike):
|
||||||
|
def __init__(self, value, /, fields):
|
||||||
|
self._value = Value.cast(value)
|
||||||
|
self._fields = list(fields)
|
||||||
|
for format in self._fields:
|
||||||
|
if not isinstance(format, (Format, Format.Enum, Format.Struct, Format.Array)):
|
||||||
|
raise TypeError(f"Field format must be a 'Format', not {format!r}")
|
||||||
|
|
||||||
|
def _as_format(self):
|
||||||
|
chunks = ["["]
|
||||||
|
for idx, field in enumerate(self._fields):
|
||||||
|
if idx != 0:
|
||||||
|
chunks.append(", ")
|
||||||
|
chunks += field._as_format()._chunks
|
||||||
|
chunks.append("]")
|
||||||
|
return Format._from_chunks(chunks)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
fields = "".join(
|
||||||
|
f" {field!r}"
|
||||||
|
for field in self._fields
|
||||||
|
)
|
||||||
|
return f"(format-array {self._value!r}{fields})"
|
||||||
|
|
||||||
|
|
||||||
class _StatementList(list):
|
class _StatementList(list):
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return "({})".format(" ".join(map(repr, self)))
|
return "({})".format(" ".join(map(repr, self)))
|
||||||
|
@ -2872,8 +2964,10 @@ class Property(Statement, MustUse):
|
||||||
self._test = Value.cast(test)
|
self._test = Value.cast(test)
|
||||||
if isinstance(message, str):
|
if isinstance(message, str):
|
||||||
message = Format._from_chunks([message])
|
message = Format._from_chunks([message])
|
||||||
if message is not None and not isinstance(message, Format):
|
if message is not None:
|
||||||
raise TypeError(f"Property message must be None, str, or Format, not {message!r}")
|
if not isinstance(message, _FormatLike):
|
||||||
|
raise TypeError(f"Property message must be None, str, or Format, not {message!r}")
|
||||||
|
message = message._as_format()
|
||||||
self._message = message
|
self._message = message
|
||||||
del self._MustUse__silence
|
del self._MustUse__silence
|
||||||
|
|
||||||
|
|
|
@ -1736,6 +1736,97 @@ class FormatTestCase(FHDLTestCase):
|
||||||
f"{fmt}"
|
f"{fmt}"
|
||||||
|
|
||||||
|
|
||||||
|
class FormatEnumTestCase(FHDLTestCase):
|
||||||
|
def test_construct(self):
|
||||||
|
a = Signal(3)
|
||||||
|
fmt = Format.Enum(a, {1: "A", 2: "B", 3: "C"})
|
||||||
|
self.assertRepr(fmt, "(format-enum (sig a) (1 'A') (2 'B') (3 'C'))")
|
||||||
|
self.assertRepr(Format("{}", fmt), """
|
||||||
|
(format '{:s}' (switch-value (sig a)
|
||||||
|
(case 001 (const 8'd65))
|
||||||
|
(case 010 (const 8'd66))
|
||||||
|
(case 011 (const 8'd67))
|
||||||
|
(default (const 72'd1723507152241428428123))
|
||||||
|
))
|
||||||
|
""")
|
||||||
|
|
||||||
|
class MyEnum(Enum):
|
||||||
|
A = 0
|
||||||
|
B = 3
|
||||||
|
C = 4
|
||||||
|
|
||||||
|
fmt = Format.Enum(a, MyEnum)
|
||||||
|
self.assertRepr(fmt, "(format-enum (sig a) (0 'A') (3 'B') (4 'C'))")
|
||||||
|
self.assertRepr(Format("{}", fmt), """
|
||||||
|
(format '{:s}' (switch-value (sig a)
|
||||||
|
(case 000 (const 8'd65))
|
||||||
|
(case 011 (const 8'd66))
|
||||||
|
(case 100 (const 8'd67))
|
||||||
|
(default (const 72'd1723507152241428428123))
|
||||||
|
))
|
||||||
|
""")
|
||||||
|
|
||||||
|
def test_construct_wrong(self):
|
||||||
|
a = Signal(3)
|
||||||
|
with self.assertRaisesRegex(TypeError,
|
||||||
|
r"^Variant values must be integers, not 'a'$"):
|
||||||
|
Format.Enum(a, {"a": "B"})
|
||||||
|
with self.assertRaisesRegex(TypeError,
|
||||||
|
r"^Variant names must be strings, not 123$"):
|
||||||
|
Format.Enum(a, {1: 123})
|
||||||
|
|
||||||
|
|
||||||
|
class FormatStructTestCase(FHDLTestCase):
|
||||||
|
def test_construct(self):
|
||||||
|
sig = Signal(3)
|
||||||
|
fmt = Format.Struct(sig, {"a": Format("{}", sig[0]), "b": Format("{}", sig[1:3])})
|
||||||
|
self.assertRepr(fmt, """
|
||||||
|
(format-struct (sig sig)
|
||||||
|
('a' (format '{}' (slice (sig sig) 0:1)))
|
||||||
|
('b' (format '{}' (slice (sig sig) 1:3)))
|
||||||
|
)
|
||||||
|
""")
|
||||||
|
self.assertRepr(Format("{}", fmt), """
|
||||||
|
(format '{{a={}, b={}}}'
|
||||||
|
(slice (sig sig) 0:1)
|
||||||
|
(slice (sig sig) 1:3)
|
||||||
|
)
|
||||||
|
""")
|
||||||
|
|
||||||
|
def test_construct_wrong(self):
|
||||||
|
sig = Signal(3)
|
||||||
|
with self.assertRaisesRegex(TypeError,
|
||||||
|
r"^Field names must be strings, not 1$"):
|
||||||
|
Format.Struct(sig, {1: Format("{}", sig[1:3])})
|
||||||
|
with self.assertRaisesRegex(TypeError,
|
||||||
|
r"^Field format must be a 'Format', not \(slice \(sig sig\) 1:3\)$"):
|
||||||
|
Format.Struct(sig, {"a": sig[1:3]})
|
||||||
|
|
||||||
|
|
||||||
|
class FormatArrayTestCase(FHDLTestCase):
|
||||||
|
def test_construct(self):
|
||||||
|
sig = Signal(4)
|
||||||
|
fmt = Format.Array(sig, [Format("{}", sig[0:2]), Format("{}", sig[2:4])])
|
||||||
|
self.assertRepr(fmt, """
|
||||||
|
(format-array (sig sig)
|
||||||
|
(format '{}' (slice (sig sig) 0:2))
|
||||||
|
(format '{}' (slice (sig sig) 2:4))
|
||||||
|
)
|
||||||
|
""")
|
||||||
|
self.assertRepr(Format("{}", fmt), """
|
||||||
|
(format '[{}, {}]'
|
||||||
|
(slice (sig sig) 0:2)
|
||||||
|
(slice (sig sig) 2:4)
|
||||||
|
)
|
||||||
|
""")
|
||||||
|
|
||||||
|
def test_construct_wrong(self):
|
||||||
|
sig = Signal(3)
|
||||||
|
with self.assertRaisesRegex(TypeError,
|
||||||
|
r"^Field format must be a 'Format', not \(slice \(sig sig\) 1:3\)$"):
|
||||||
|
Format.Array(sig, [sig[1:3]])
|
||||||
|
|
||||||
|
|
||||||
class PrintTestCase(FHDLTestCase):
|
class PrintTestCase(FHDLTestCase):
|
||||||
def test_construct(self):
|
def test_construct(self):
|
||||||
a = Signal()
|
a = Signal()
|
||||||
|
|
Loading…
Reference in a new issue