Expand description
The Python backend of the shared codec walk.
DDS-XTypes 1.3 §7.4 from the clause, the same as runtime/c,
runtime/java, runtime/typescript and the rest do, and none of
them calls another. Where two columns disagree on an octet, one has
misread the specification — which is the only reason to have a sixth.
§The four decisions this column turns on
An int is unbounded, so a width is never a cast. Every other
backend spends part of itself on narrowing: (int) in Java,
static_cast in C++, zdNumber/zdBigInt in TypeScript, because a
value that arrived wider than its holder is a value already lost.
Python has one integer type and it holds every IDL width exactly, so
CodecTarget::cast is the identity on every integer and the width
lives where it belongs — in the codec’s name, which
adapter::python::type_tag builds from the IDL kind and never from
the Python spelling. A tag built from the Python type would give
octet, unsigned short and unsigned long long one codec.
An assignment is a statement. a = b is not an expression in
Python: (a = b) is a SyntaxError. The shared walk builds one
through T::perform(T::binary("=", …)) at every assignment site but
two, so Python::perform recognises that shape and answers with
py::Statement::Set. Nothing else in the tree can hold an =.
A block is an indent. There is no delimiter, so an empty suite is
not untidy output — it is a SyntaxError, and there is no program the
walk could have meant. The four constructors that take a body
(Python::when, Python::count_up, Python::until,
Python::routine) pad an empty one with pass here, where they can
say which body was empty; the renderer’s own guard is the backstop.
A long double is a Float128 and never a float. A Python
float is a binary64 with 53 significand bits and a long double is
a binary128 with 113. runtime/python’s Float128 carries the
sixteen octets, and this column reaches for it exactly as the
TypeScript one reaches for ZdFloat128.
§The one place this file and the materialiser disagree
adapter/python.rs declares a long double member as
_Float128 = typing.Annotated[str, _IdlFloat(bits=128)] — the
decimal text — and argues for it: a materialised module imports
nothing but dataclasses, enum and typing, so a runtime class
there would make every generated file depend on a layer the
materialiser is not part of. Python::scalar answers Float128.
The runtime settles which of the two a codec may use, and it is not
this file’s preference: Writer.write_f128 takes Float128 | float
and Reader.read_f128 answers a Float128. Neither takes a str,
so a codec built on the materialiser’s spelling would hand a decimal
string to a method that has no branch for one.
Nothing enforces the annotation at run time, so the two disagree on
paper and not on the wire — primitive-longdouble and
sequence-double-and-longdouble are byte-identical in both
directions under all ten encoding and byte-order pairs. What it does
mean is that a generated module carrying both would not pass a strict
type check, and the answer is one of two decisions neither of which
is a codec’s to take: the materialiser reaches for the runtime class
and gives up its independence from this layer, or the codec converts
at the member boundary — for which the shared walk offers no seam,
because ScalarWrite::F128 hands the member straight to
write_f128 with no cast in between.
§What used to sit beside this file
crates/lang-python/src/emit.rs was a second Python emitter, written
against text rather than against a tree, driven by zerodds-idl-python
and tools/wireprobe/wireprobe-zerodds-python. It carried its own
copy of the walk — which members go out, in what order, under which
header — and that copy was the thing this file exists to stop
needing: tools/wireprobe/wireprobe-zerodds-python-gen and
tools/idlc/src/python_support.rs are built on this file now, the
same as the C#, Java, C, C++ and TypeScript columns are built on
their own, and crates/lang-python is gone.
Structs§
- Python
- The Python backend.