zerodds_lang_rules/emit/mod.rs
1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2026 ZeroDDS Contributors
3//! The target a codec walk emits into.
4//!
5//! [`encode`] walks the layout plan and decides, member by
6//! member, what the generated program does: which header opens, what the
7//! length code is, in what order a `map`'s entries go, where a `DHEADER`
8//! is closed. None of that is C. What is C is the shape the decision is
9//! written in — `v->x` rather than `v.x`, `&v->x` rather than `v.x` at a
10//! call site, a counter declared `uint32_t` rather than `int`.
11//!
12//! This trait is that seam. A walk written against it emits the same
13//! decisions into any language whose runtime implements the wire, and a
14//! new column costs a backend rather than a second copy of the walk.
15//!
16//! ## What this does not share, on purpose
17//!
18//! Not the runtime. `runtime/c`, `runtime/cpp`, `runtime/java` and
19//! `runtime/python` each implement DDS-XTypes 1.3 §7.4 from the clause,
20//! and that is what makes a column a second reading of the specification
21//! rather than a rendering of the first one's answer. What a backend
22//! contributes here is call construction: which method name, which
23//! receiver, which argument form. Where two columns disagree on a byte,
24//! the disagreement is still between two runtimes.
25//!
26//! Sharing the walk removes the other thing — four copies of one framing
27//! decision, of which a later clause fix reaches three. That failure
28//! leaves every test green.
29//!
30//! ## Moving one of these files
31//!
32//! Several guards key on the path of a walk rather than on its contents,
33//! deliberately: they check that exactly one site in the encoder crosses
34//! from a declared bound to the effective one, and a check that searched
35//! the whole tree would pass on a site in a test. So a rename breaks
36//! them, and it broke them twice while these two walks moved here.
37//!
38//! What to sweep, both times it was these:
39//!
40//! - `tools/silent-drop-allowlist.tsv` — keyed on path *and* text, so a
41//! turbofish added by a genericisation counts as a new site. Move the
42//! reasons rather than re-judging: the code in the function did not
43//! change, and a reason written a second time is a reason that has
44//! drifted from what it was written about.
45//! - `crates/wire-probe/tests/{enum_holder_width,enumerator_value,
46//! fixed_digit_count,map_entry_order,union_label_match}.rs` — one
47//! `SITES` table each.
48//! - `docs/{c-column,wire-feature-inventory,wire-axes-followup}.md` and
49//! the interop-suite spec.
50//!
51//! `cargo test --workspace` stops after the first failing binary, which
52//! is why the first sweep looked like one guard and was five. Use
53//! `--no-fail-fast`.
54//!
55//! ## The scalar set
56//!
57//! [`Scalar`] is the C set because the C set is the union: every other
58//! target has a name for each of these, and none needs one the list has
59//! not got. `LongDouble` is the awkward member and is kept abstract for
60//! the same reason `codec_name` refuses to name a function after the C
61//! spelling — `double` and `long double` share a C type and do not share
62//! a wire form.
63
64pub mod ada;
65pub mod ada83;
66pub mod c;
67pub mod cpp;
68pub mod csharp;
69pub mod d;
70pub mod decode;
71pub mod elixir;
72pub mod encode;
73pub mod fsharp;
74pub mod go;
75pub mod interface_cpp;
76pub mod interface_java;
77pub mod interface_rust;
78pub mod interface_typescript;
79pub mod java;
80pub mod julia;
81pub mod kotlin;
82pub mod lua;
83pub mod nim;
84pub mod node;
85pub mod ocaml;
86pub mod python;
87pub mod swift;
88pub mod type_pair_cpp;
89pub mod type_pair_java;
90pub mod type_pair_rust;
91pub mod typescript;
92pub mod zig;
93
94use crate::encode::EncodeError;
95use zerodds_ast::{Ast, MemberNode, TypeNode};
96use zerodds_ast_core::Id;
97use zerodds_lang_cst::common::{Doc, Literal};
98
99/// Whether `id` is an enumeration once typedefs are seen through.
100///
101/// [`CodecTarget::enumerator_ordinal`] is asked for a map key of an
102/// enumeration type and for the integer arms of a union overlay alike,
103/// and a target whose override converts an enumeration value (`.rawValue`,
104/// `@intFromEnum`) has to leave the integer arms as they are: the overlay
105/// never carries an enumeration
106/// ([`zerodds_ast_layout::union_negative_matches_nothing`]).
107pub(crate) fn resolves_to_enumeration(ast: &Ast, id: Id<TypeNode>) -> bool {
108 const MAX_ALIAS_DEPTH: usize = 64;
109 let mut current = id;
110 for _ in 0..MAX_ALIAS_DEPTH {
111 match ast.type_(current).map(|node| &node.kind) {
112 Ok(zerodds_ast::TypeKind::Enumeration { .. }) => return true,
113 Ok(zerodds_ast::TypeKind::Alias { aliased }) => match aliased.resolved() {
114 Some(target) => current = target,
115 None => return false,
116 },
117 _ => return false,
118 }
119 }
120 false
121}
122
123/// Whether the walk reaches `member` through `T`'s `external_` seams:
124/// it carries `@external`, or `T` keeps a member on a type cycle the
125/// same way ([`CodecTarget::holds_a_cycle_externally`]). One answer for
126/// the four places the walks ask it, so the reader, the writer and the
127/// union overlay cannot hold one member two ways.
128pub(crate) fn reached_externally<T: CodecTarget>(
129 ast: &Ast,
130 member: &zerodds_ast::MemberNode,
131) -> bool {
132 if T::holds_a_cycle_externally() {
133 // A column that holds `@optional` members behind a pointer of
134 // their own (`T *`, §7.5.1.2.4.2) holds one that closes a cycle
135 // there too: it is optional before it is external.
136 if T::optionals_are_pointers()
137 && member.facets.is_optional()
138 && !crate::adapter::annotated_external(member)
139 {
140 return false;
141 }
142 crate::adapter::held_externally(ast, member)
143 } else {
144 crate::adapter::annotated_external(member)
145 }
146}
147
148/// Whether this column keeps a member behind an indirection that
149/// [`CodecTarget::behind_indirection`] reaches through: the member
150/// closes a cycle, or it is `@external` in a column that keeps externals
151/// behind a pointer ([`CodecTarget::externals_are_pointers`]) and its
152/// value is not a pointer already.
153///
154/// `external` is the answer of [`reached_externally`] for the member.
155/// One answer for the encoder, the decoder and the defaulter, so that
156/// what the header declares and what the codec reaches through cannot
157/// part.
158pub(crate) fn behind_pointer<T: CodecTarget>(
159 ast: &Ast,
160 enclosing: Id<TypeNode>,
161 member_type: Id<TypeNode>,
162 external: bool,
163 optional: bool,
164) -> bool {
165 zerodds_ast_layout::member_held_inline_closes_a_cycle(ast, enclosing, member_type)
166 || (((external && T::externals_are_pointers())
167 || (optional && !external && T::optionals_are_pointers()))
168 && !T::value_is_pointer(ast, member_type))
169}
170
171/// How a walk reaches one member of an aggregate: the spelling of its field
172/// and the structure that declares it.
173pub(crate) struct Reach {
174 /// The field, with the path through the base members that a column
175 /// binds as `parent` ([`CodecTarget::inherited_path`]).
176 pub field: String,
177 /// The structure whose declaration holds the member: `aggregate`, or the
178 /// base it is inherited from where the column nests the base.
179 pub holder: Id<TypeNode>,
180}
181
182/// How many structures up the base chain of `aggregate` declare `member`.
183pub(crate) fn inheritance_depth(
184 ast: &Ast,
185 aggregate: Id<TypeNode>,
186 member: zerodds_ast_core::NodeId,
187) -> usize {
188 let mut current = aggregate;
189 for depth in 0..64 {
190 let declares = ast
191 .members_of(current)
192 .iter()
193 .any(|handle| ast.member(*handle).is_ok_and(|node| node.node_id == member));
194 if declares {
195 return depth;
196 }
197 match ast.type_(current).map(|node| &node.kind) {
198 Ok(zerodds_ast::TypeKind::Structure {
199 base: Some(base), ..
200 }) => match base.resolved() {
201 Some(next) => current = next,
202 None => return 0,
203 },
204 _ => return 0,
205 }
206 }
207 0
208}
209
210/// The structure `depth` steps up the base chain of `aggregate`.
211pub(crate) fn base_at(ast: &Ast, aggregate: Id<TypeNode>, depth: usize) -> Id<TypeNode> {
212 let mut current = aggregate;
213 for _ in 0..depth {
214 match ast.type_(current).map(|node| &node.kind) {
215 Ok(zerodds_ast::TypeKind::Structure {
216 base: Some(base), ..
217 }) => match base.resolved() {
218 Some(next) => current = next,
219 None => return aggregate,
220 },
221 _ => return aggregate,
222 }
223 }
224 current
225}
226
227/// Where the walk of `aggregate` finds `member`, named `name` in IDL.
228///
229/// XTypes 1.3 §7.5.1.4.1.3: a language with no mapping of its own for
230/// inheritance binds a derived structure "as if an instance of the base type
231/// were the first member of the subtype with the name 'parent'". A column that
232/// does answers [`CodecTarget::inherited_path`], and a member of a base is
233/// reached through it; one that does not keeps the base's members in the
234/// derived structure, where `field` is the plain name and `holder` is the
235/// aggregate itself.
236/// The base of a derived structure and the names of every member it has, inherited ones
237/// included. XTypes 1.3 §7.5.1.4.1.3: a language with no mapping of its own for inheritance
238/// binds the base as the first member `parent`; the code that fills a sample takes these keys
239/// out of the flat object it is given and fills the base from them. `None` for a structure
240/// with no base.
241#[must_use]
242pub fn inherited_member_names(
243 ast: &Ast,
244 aggregate: Id<TypeNode>,
245) -> Option<(Id<TypeNode>, Vec<String>)> {
246 let base = crate::adapter::c::struct_base(ast, aggregate)?;
247 let names = ast
248 .all_members_of(base)
249 .iter()
250 .filter_map(|id| ast.member(*id).ok())
251 .map(|member| member.name.clone())
252 .collect();
253 Some((base, names))
254}
255
256/// The field a walk of `aggregate` writes `name` as, through the base members a column binds
257/// as `parent` (see `reach`); for the code that fills and prints a sample of a generated type.
258#[must_use]
259pub fn member_field<T: CodecTarget>(
260 ast: &Ast,
261 aggregate: Id<TypeNode>,
262 member: zerodds_ast_core::NodeId,
263 name: &str,
264) -> String {
265 reach::<T>(ast, aggregate, member, name).field
266}
267
268pub(crate) fn reach<T: CodecTarget>(
269 ast: &Ast,
270 aggregate: Id<TypeNode>,
271 member: zerodds_ast_core::NodeId,
272 name: &str,
273) -> Reach {
274 let escaped = T::escape(name);
275 let depth = inheritance_depth(ast, aggregate, member);
276 match (depth, T::inherited_path(depth)) {
277 (0, _) | (_, None) => Reach {
278 field: escaped,
279 holder: aggregate,
280 },
281 (depth, Some(path)) => Reach {
282 field: format!("{path}{escaped}"),
283 holder: base_at(ast, aggregate, depth),
284 },
285 }
286}
287
288/// The enumerator an enumeration takes as its default, by IDL name.
289///
290/// §7.3.1.2.1.10: the one `@default_literal` names, and "the first
291/// literal in the enumeration" where none does. A question about the
292/// specification and not about a language, so it is answered once here
293/// and each column spells the answer through
294/// [`CodecTarget::enumerator_constant`]. `default_c` derives the same
295/// thing for its own defaulters and takes it from here, so the reader's
296/// fallback and the defaulter's cannot name two different enumerators.
297#[must_use]
298pub fn default_enumerator_name(ast: &Ast, id: Id<TypeNode>) -> Option<String> {
299 let mut first = None;
300 for enumerator in ast.enumerators_of(id) {
301 let Ok(node) = ast.enumerator(*enumerator) else {
302 continue;
303 };
304 if node
305 .facets
306 .xtypes
307 .as_ref()
308 .is_some_and(|x| x.is_default_literal)
309 {
310 return Some(node.name.clone());
311 }
312 if first.is_none() {
313 first = Some(node.name.clone());
314 }
315 }
316 first
317}
318
319/// A scalar the generated program declares a local of.
320///
321/// Locals only. A member's own type comes from the language's
322/// materialiser, which knows things this does not: what a `sequence`
323/// widens to, whether an `@optional` is a nullable reference or a
324/// wrapper. These are the counters, lengths and saved header positions a
325/// codec body needs for itself.
326#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
327pub enum Scalar {
328 /// The natural type of a `for` counter and a container length.
329 ///
330 /// `uint32_t` in C, `std::size_t` in C++, `int` in Java — the
331 /// backend's own answer, and deliberately not one of the fixed-width
332 /// members below. A sequence length is a `uint32` on the wire, so C
333 /// counts in that type and `-Wsign-conversion` stays quiet where the
334 /// counter meets one; Java has no unsigned type to count in and a
335 /// `long` counter would need a cast at every `List.get`.
336 Index,
337 /// The type a byte count and a stream position are held in.
338 ///
339 /// `size_t` in C and `int` in Java, and distinct from
340 /// [`Scalar::Index`] because C's two are not the same type: a
341 /// reader compares a position against a delimiter's end in
342 /// `size_t` and counts elements in `uint32_t`, and mapping both to
343 /// one of the two is a narrowing `-Wshorten-64-to-32` catches.
344 Size,
345 /// `bool`
346 Bool,
347 /// `char`, the octet-wide one.
348 Char,
349 /// `int8_t`
350 Int8,
351 /// `int16_t`
352 Int16,
353 /// `int32_t`
354 Int32,
355 /// `int64_t`
356 Int64,
357 /// `uint8_t`
358 UInt8,
359 /// `uint16_t`
360 UInt16,
361 /// `uint32_t`
362 UInt32,
363 /// `uint64_t`
364 UInt64,
365 /// `float`
366 Float,
367 /// `double`
368 Double,
369 /// binary128, whatever the target calls it.
370 LongDouble,
371}
372
373/// A small value of the runtime's that the generated code declares.
374///
375/// A header is opened before the value it frames and closed after it,
376/// and the close needs to know where the open wrote. Each runtime keeps
377/// that in a small value of its own — `zdw_dheader` in C, `DHeader` in
378/// Java — and the walk holds one in a local without knowing what it is.
379///
380/// Naming these in the walk was the last thing C's spelling reached
381/// into: a Java backend that inherited `zdw_dheader` as a type name
382/// would generate source no compiler has a type for.
383///
384/// The reading direction adds four, and they were the leak the C++
385/// reader found: the walk spelled `zdw_try_construct`,
386/// `zdw_reader_mark`, `zdw_pid_read` and `zdw_emheader_read` into
387/// [`CodecTarget::named`] directly, which is C's namespace written into
388/// what is supposed to be language-neutral. They went unnoticed because
389/// the C column was the only one that generated a reader.
390#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
391pub enum Handle {
392 /// What [`CodecTarget::runtime`]'s `write_dheader` returns.
393 DHeader,
394 /// What `write_emheader` returns.
395 EmHeader,
396 /// What `write_pid` and `write_pid_extended` return.
397 Pid,
398 /// The behaviour §7.2.2.7 attaches to a position, as the reader's
399 /// extra parameter takes it.
400 ///
401 /// `zdw_try_construct` in C, `TryConstruct` in Java and C++. A
402 /// parameter rather than something the runtime hands back, and here
403 /// rather than beside [`Constant`] because the walk needs the
404 /// *type* to declare the parameter with; the three values it takes
405 /// are `Constant::TryConstruct*`. A walk that wrote C's spelling
406 /// would name a type `javac` has never seen.
407 TryConstruct,
408 /// What `reader_save` returns and `reader_restore` takes.
409 ReaderMark,
410 /// What `read_pid` and `read_pid_as` return.
411 PidRead,
412 /// What `read_emheader` returns.
413 EmHeaderRead,
414}
415
416/// What a codec routine is made of.
417///
418/// A struct rather than a parameter list because the reader takes a
419/// parameter the writer does not — the try-construct behaviour, which
420/// DDS-XTypes 1.3 §7.2.2.7 attaches to the position rather than to the
421/// type — and eight positional arguments is where a caller starts
422/// passing them in the wrong order.
423pub struct Routine<T: CodecTarget + ?Sized> {
424 /// The comment above it.
425 pub doc: Doc,
426 /// Its name.
427 pub name: String,
428 /// The type of the value being coded.
429 pub sample: T::Type,
430 /// How the sample arrives. See [`Handoff`].
431 pub held: Handoff,
432 /// Whether this is the writing direction.
433 pub writing: bool,
434 /// Whether it may legitimately have no caller.
435 pub tolerate_unused: bool,
436 /// Parameters after the sample, in written order.
437 pub extra: Vec<(T::Type, String)>,
438 /// The body, absent to ask for a forward declaration.
439 pub body: Option<T::Block>,
440}
441
442/// A policy value the generated code names.
443///
444/// Every runtime publishes the same set — they implement the same
445/// clauses — and each spells them its own way: `ZDW_WCHAR_WIDTH_2` in C
446/// against `WcharWidth.OCTETS_2` in Java. An enum rather than the C
447/// string, so a backend that forgets one does not compile instead of
448/// emitting a name javac has never seen.
449#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
450pub enum Constant {
451 /// A scalar `wchar` is two octets wide.
452 WcharWidth2,
453 /// A scalar `wchar` is four octets wide.
454 WcharWidth4,
455 /// A `wchar` carries no length octet.
456 WcharFixed,
457 /// A `wchar` carries GIOP's length octet.
458 WcharGiopLengthPrefixed,
459 /// A `wstring`'s code units go in the stream's byte order.
460 WstringUnitsStream,
461 /// Big-endian whatever the stream is.
462 WstringUnitsBigEndianAlways,
463 /// Preceded by a byte-order mark.
464 WstringUnitsByteOrderMark,
465 /// In the host's order, unswapped.
466 WstringUnitsHostUnswapped,
467 /// §7.4.1.1.2: octets, no terminator.
468 WstringSpecification,
469 /// Characters rather than octets.
470 WstringCharacterCount,
471 /// Characters, and a terminator.
472 WstringCharacterCountTerminated,
473 /// Octets in wide units.
474 WstringSpecificationWideUnits,
475 /// What `rosidl` writes.
476 WstringRosTypeSupport,
477 /// Octets, terminated, two-octet units.
478 WstringOctetsTerminated2,
479 /// Octets, terminated, four-octet units.
480 WstringOctetsTerminated4,
481 /// Characters, terminated, two-octet units.
482 WstringCharactersTerminated2,
483 /// A `fixed` writes the digits its type declared.
484 FixedDeclared,
485 /// A `fixed` writes as many digits as the value has.
486 FixedValuePrecision,
487 /// A `fixed` writes the digit count of the input string, padded to odd.
488 FixedValueStringDigits,
489 /// A PL_CDR1 parameter carries a member.
490 ParameterMember,
491 /// A PL_CDR1 parameter carries an `@optional` member.
492 ParameterOptional,
493 /// §7.2.2.7: the sample is discarded.
494 TryConstructDiscard,
495 /// The value is trimmed to what fits.
496 TryConstructTrim,
497 /// The member takes Table 9's value.
498 TryConstructUseDefault,
499 /// The reader is still good.
500 StatusOk,
501 /// The reader gave up: the sample is discarded.
502 StatusDiscarded,
503 /// A length no stream that long can carry.
504 StatusImpossibleLength,
505 /// A value outside what the type can hold.
506 StatusOutOfRange,
507 /// The stream ended where a value was expected.
508 StatusTruncated,
509}
510
511/// How a value reaches a nested codec.
512///
513/// The one place the targets genuinely disagree, and the reason it is an
514/// enum rather than a `bool`: C hands an aggregate's address and an
515/// array's first element, which are written differently and mean the
516/// same thing, while a target with references hands the value either
517/// way. A caller says which of the two situations it is in and the
518/// backend says how that is spelled.
519#[derive(Debug, Clone, Copy, PartialEq, Eq)]
520pub enum Handoff {
521 /// The value is an array, which C adjusts to a pointer on its own.
522 Array,
523 /// Anything else.
524 Value,
525}
526
527/// What a union branch's storage octets are read out of.
528///
529/// The Cyclone union overlay (`union_negative_selects_nothing`): a
530/// discriminator that selects a suppressed negative label makes the
531/// peer write its union storage *as the default arm*, so the emitted
532/// codec has to produce the value's storage bits — the `u64` whose
533/// little-endian bytes are the value's little-endian storage octets,
534/// zero-extended. Sign extension is the mistake this enum exists to
535/// prevent: a target with no unsigned types masks, it does not cast.
536#[derive(Debug, Clone, Copy, PartialEq, Eq)]
537pub enum BitPatternSource {
538 /// An integer, character, enumeration or bitmask holder of this
539 /// many octets. The pattern is the value's two's-complement bits,
540 /// masked to the width — `-2` in four octets is
541 /// `0x00000000FFFFFFFE`. The walk hands the value through the
542 /// target's `Int64` cast first, the same seam the enumeration
543 /// codec reads its value through, so every target receives its
544 /// widest integer and only masks.
545 Int {
546 /// 1, 2, 4 or 8.
547 octets: u8,
548 },
549 /// A boolean: one octet, 0 or 1.
550 Bool,
551 /// Binary32: the IEEE 754 bits, zero-extended.
552 Float32,
553 /// Binary64: the IEEE 754 bits.
554 Float64,
555}
556
557/// The syntax a codec walk builds.
558///
559/// Every method is a constructor: the trait holds no state and a backend
560/// is a unit type. What varies between backends is spelling, not order —
561/// a walk calls these in the sequence the layout plan dictates and the
562/// backend never reorders.
563pub trait CodecTarget {
564 /// An expression.
565 type Expr: Clone;
566 /// A statement.
567 type Stmt: Clone;
568 /// A sequence of statements.
569 type Block: Clone;
570 /// A type, as written in a declaration.
571 type Type: Clone;
572 /// A top-level declaration — in C a function, in Java a method.
573 type Decl;
574
575 // -- types --------------------------------------------------------
576
577 /// A scalar local's type.
578 fn scalar(kind: Scalar) -> Self::Type;
579
580 /// A type named by the materialiser.
581 fn named(name: &str) -> Self::Type;
582
583 /// The type of a handle the runtime hands back. See [`Handle`].
584 fn handle(kind: Handle) -> Self::Type;
585
586 /// A policy value the runtime publishes. See [`Constant`].
587 fn constant(which: Constant) -> Self::Expr;
588
589 /// A character's code point, as the runtime's parameter wants it.
590 ///
591 /// `narrow` is a `char` rather than a `wchar`: C truncates to an
592 /// octet and widens again, which is what its runtime is handed
593 /// today and what 17 220 recorded slots agree on. A target whose
594 /// character type is already a code unit hands it over and lets the
595 /// runtime keep the low byte — the same octet, decided one step
596 /// later.
597 ///
598 /// A seam and not a cast, because the width is the backend's:
599 /// `Scalar::UInt32` is `uint32_t` in C and `long` in Java, and
600 /// `writeChar` takes an `int`.
601 fn code_point(value: Self::Expr, narrow: bool) -> Self::Expr;
602
603 /// Whether `left` is less than `right` as **unsigned** integers of
604 /// this width.
605 ///
606 /// The default is the target's own `<`, which is right wherever the
607 /// held type is unsigned or strictly wider than the value. Five
608 /// columns are not in that position at eight octets, and each
609 /// overrides: Java and Kotlin hold an IDL `unsigned long long` as
610 /// the bit pattern in a `long` and reach `Long.compareUnsigned`,
611 /// Lua reaches `math.ult`, OCaml's `Int64` reaches its runtime's
612 /// `u64_less`, and Ada 83 — which has no modular type at all, only
613 /// `Long_Integer` — reaches `Zd_Wire83.U64_Less`. Every one of them
614 /// sorted `16045690984833335998` ahead of `7` in a `map`'s
615 /// ascending-key order before the override existed (measured
616 /// against Fast DDS on `map-key-ulonglong`; Ada 83 against Fast DDS
617 /// and OpenDDS, all four encodings, 2026-09-04). A target in that
618 /// position overrides.
619 ///
620 /// The narrower widths need no override anywhere: an `unsigned
621 /// long` tops out at 4294967295, which every column above holds
622 /// zero-extended in a signed type at least 64 bits wide, so the
623 /// signed order and the unsigned order are the same order.
624 fn unsigned_less(octets: u8, left: Self::Expr, right: Self::Expr) -> Self::Expr {
625 let _ = octets;
626 Self::binary("<", left, right)
627 }
628
629 /// Whether `left` is less than `right` as **boolean** map keys —
630 /// `false` before `true`, the ascending-key order's reading of the
631 /// one-octet wire value.
632 ///
633 /// The default is the target's own `<`, which C, C++, Rust and
634 /// Python all give two booleans. A language whose `<` refuses the
635 /// pair — Java, C# and TypeScript name a type error — overrides
636 /// with a spelling of the same order.
637 fn bool_less(left: Self::Expr, right: Self::Expr) -> Self::Expr {
638 Self::binary("<", left, right)
639 }
640
641 /// The value's storage bits, zero-extended into the runtime's
642 /// 64-bit pattern argument. See [`BitPatternSource`]; consumed by
643 /// the runtime's `write_union_overlay`, which writes the low
644 /// `octets` of the pattern as one integer of that width in wire
645 /// endianness — the Cyclone union overlay.
646 ///
647 /// The default spells each form as a free function of the runtime —
648 /// `bit_pattern_int(value, octets)`, `bit_pattern_bool`,
649 /// `bit_pattern_f32`, `bit_pattern_f64` — which fits every target
650 /// whose [`CodecTarget::runtime_static`] names free functions. A
651 /// target that routes `runtime_static` elsewhere, or that has a
652 /// native spelling for a form, overrides.
653 fn bit_pattern(source: BitPatternSource, value: Self::Expr) -> Self::Expr {
654 match source {
655 BitPatternSource::Int { octets } => Self::runtime_static(
656 "bit_pattern_int",
657 vec![value, Self::int(i128::from(octets))],
658 ),
659 BitPatternSource::Bool => Self::runtime_static("bit_pattern_bool", vec![value]),
660 BitPatternSource::Float32 => Self::runtime_static("bit_pattern_f32", vec![value]),
661 BitPatternSource::Float64 => Self::runtime_static("bit_pattern_f64", vec![value]),
662 }
663 }
664
665 /// The unsigned type wide enough to hold `bits` of a bit set.
666 ///
667 /// A `@bit_bound(12)` set is assembled in something at least twelve
668 /// bits wide, and which type that is belongs to the target: C picks
669 /// the smallest `uintN_t` that fits, and a language with no unsigned
670 /// types picks the next signed one up and lets the runtime read the
671 /// bits. The walk asks for room, not for a spelling.
672 fn unsigned_holder(bits: u16) -> Self::Type;
673
674 /// An operation on the runtime that has no receiver.
675 ///
676 /// `no_dheader` is the only one: the plan says a value takes no
677 /// delimiter and the walk still needs a handle to hand the close,
678 /// so the runtime supplies one that finishes nothing. C exports it
679 /// as a free function and Java as a static method on the writer,
680 /// which is the same difference as [`CodecTarget::runtime`] and not
681 /// the same call.
682 fn runtime_static(method: &str, args: Vec<Self::Expr>) -> Self::Expr;
683
684 /// What a codec function returns when it returns nothing.
685 fn nothing() -> Self::Type;
686
687 // -- expressions --------------------------------------------------
688
689 /// A bare name.
690 fn name(text: &str) -> Self::Expr;
691
692 /// An integer literal.
693 fn int(value: i128) -> Self::Expr;
694
695 /// Any other literal the common tree can hold.
696 fn literal(value: Literal) -> Self::Expr;
697
698 /// A call to a free function or, in a target that has no free
699 /// functions, to the static method standing in for one.
700 fn call(callee: &str, args: Vec<Self::Expr>) -> Self::Expr;
701
702 /// An operation on the runtime's writer or reader.
703 ///
704 /// `method` is the operation's bare name — `write_u32`, `read_pid`,
705 /// `finish_dheader` — and the backend spells it: C prefixes the
706 /// runtime's single namespace and passes the receiver first,
707 /// `zdw_write_u32(w, x)`; a target with methods writes
708 /// `w.writeU32(x)`. Separate from [`CodecTarget::call`] because the
709 /// two differ in more than the name, and a walk that built the C
710 /// form and let a renderer guess at the other would be deciding a
711 /// language question in the walk.
712 ///
713 /// `receiver` is the local the body holds the writer or reader in,
714 /// which is `w` and `r` in every backend so far.
715 fn runtime(receiver: &str, method: &str, args: Vec<Self::Expr>) -> Self::Expr;
716
717 /// Whether this target's runtime carries the three axes
718 /// [`zerodds_ast_layout::Dialect::Hdds`] selects.
719 ///
720 /// The axes are [`zerodds_ast_layout::key::ElementPadding::Packed`],
721 /// [`zerodds_ast_layout::key::SequenceElementDelimiter::BeforeEachNonFinalElement`]
722 /// and [`zerodds_ast_layout::key::WstringCodeUnits::Utf8`]. The
723 /// reference is `zerodds-wire-codec`; a target whose runtime has the
724 /// packed-element origin, the element length in DHEADER form and a
725 /// UTF-8 `wstring` answers `true` and may be generated under that
726 /// dialect. The default is `false`, so a runtime that has not been
727 /// given them is refused with the axis named rather than generated in
728 /// the specification's form — a different octet string wearing this
729 /// key's name.
730 fn carries_hdds_axes() -> bool {
731 false
732 }
733
734 /// A member of a value the codec was handed.
735 ///
736 /// `v->x` where the parameter is a pointer, `v.x` where it is a
737 /// reference. Not [`CodecTarget::field_of`], which reaches into a
738 /// value the body already owns.
739 fn member(base: &str, field: &str) -> Self::Expr;
740
741 /// The whole value the codec was handed, rather than one member.
742 ///
743 /// `*v` where the parameter is a pointer, `v` where it is a
744 /// reference. What a primitive's codec writes.
745 fn whole(base: &str) -> Self::Expr;
746
747 /// An integer literal that has to survive its own width.
748 ///
749 /// A union label the size of `16045690984833335998` is not an `int`
750 /// in any of these languages, and a bare decimal is a constant whose
751 /// type the compiler picks — with a warning where the reader is
752 /// lucky and a quietly different comparison where they are not. C
753 /// writes `UINT64_C(..)`, Java an `L` suffix. Separate from
754 /// [`CodecTarget::int`] because most literals are small and the
755 /// suffixed form would be noise on all of them.
756 fn wide_int(value: i128) -> Self::Expr;
757
758 /// A count, in whatever spelling keeps the target's conversions
759 /// quiet.
760 ///
761 /// A sequence bound, an element count and a member id are all
762 /// unsigned on the wire, and C writes them with the `u` suffix so
763 /// that a comparison against a `uint32_t` does not trip
764 /// `-Wsign-compare`. Java has no unsigned literal and `5u` is not a
765 /// token there at all. The walk asks for a count and each backend
766 /// spells it.
767 fn unsigned_int(value: i128) -> Self::Expr {
768 Self::literal(Literal::Int(format!("{value}u")))
769 }
770
771 /// A 64-bit pattern, in a spelling wide enough to survive itself.
772 ///
773 /// A bitmask's declared-bit mask is sixty-four bits and a bare
774 /// decimal is a constant whose type the compiler picks. C writes
775 /// `UINT64_C(..)`; Java has no unsigned integer and writes the
776 /// signed pattern with an `L`.
777 fn wide_unsigned(value: u64) -> Self::Expr {
778 Self::literal(Literal::Int(format!("UINT64_C({value})")))
779 }
780
781 /// A signed 64-bit constant, as [`CodecTarget::wide_unsigned`].
782 fn wide_signed(value: i128) -> Self::Expr {
783 Self::literal(Literal::Int(format!("INT64_C({value})")))
784 }
785
786 /// A member of an expression.
787 fn field_of(value: Self::Expr, field: &str) -> Self::Expr;
788
789 /// `a[i]`
790 fn index(value: Self::Expr, at: Self::Expr) -> Self::Expr;
791
792 /// How many elements the collection in `base` holds.
793 ///
794 /// `_length` on the C sequence struct, `.length` on a Java array,
795 /// and for a fixed-size array the bound the materialiser fixed. The
796 /// base name and not an expression, for the reason
797 /// [`CodecTarget::elements`] takes one: how a target reaches into
798 /// its own collection is the target's.
799 fn count_of(base: &str, fixed: Option<u64>) -> Self::Expr;
800
801 /// `a op b`, written as given.
802 fn binary(op: &'static str, lhs: Self::Expr, rhs: Self::Expr) -> Self::Expr;
803
804 /// `op a`, written as given.
805 fn unary(op: &'static str, value: Self::Expr) -> Self::Expr;
806
807 /// Parentheses written on purpose.
808 ///
809 /// The trees carry no precedence, so a walk that needs a bracket
810 /// says so and every backend honours it.
811 fn paren(value: Self::Expr) -> Self::Expr;
812
813 /// A conversion the target requires and the walk knows is exact.
814 fn cast(type_: Self::Type, value: Self::Expr) -> Self::Expr;
815
816 /// A value on its way into a nested codec.
817 ///
818 /// The abstraction of `&v->x` against `v.x`; see [`Handoff`].
819 fn handoff(value: Self::Expr, form: Handoff) -> Self::Expr;
820
821 /// All of `count` octet elements of `base` at once, in place of the
822 /// element loop of a `sequence<octet>` or a one-dimensional
823 /// `octet[N]` (and the same over `uint8`); `None`, the default, keeps
824 /// the loop. The octets are the loop's: an octet has alignment 1 and
825 /// no byte order (XTypes 1.3 §7.4.3.4.1), so a run of them is the same
826 /// bytes however it is copied. What a target gains is the per-element
827 /// call — measured in the C++ column: `sequence<octet, 8192>` element
828 /// by element cost ~100 µs per roundtrip of four codec passes.
829 fn bulk_octets_write(base: &str, count: Self::Expr) -> Option<Self::Stmt> {
830 let _ = (base, count);
831 None
832 }
833
834 /// The read side of [`Self::bulk_octets_write`]: `count` octets into
835 /// `base`, which already holds room for them.
836 fn bulk_octets_read(base: &str, count: Self::Expr) -> Option<Self::Stmt> {
837 let _ = (base, count);
838 None
839 }
840
841 // -- statements ---------------------------------------------------
842
843 /// A local declaration with an optional initialiser.
844 fn declare(type_: Self::Type, name: &str, value: Option<Self::Expr>) -> Self::Stmt;
845
846 /// An expression evaluated for its effect.
847 fn perform(value: Self::Expr) -> Self::Stmt;
848
849 /// `return`, with or without a value.
850 fn give_back(value: Option<Self::Expr>) -> Self::Stmt;
851
852 /// `if (cond) { .. } else { .. }`
853 fn when(condition: Self::Expr, then: Self::Block, otherwise: Option<Self::Block>)
854 -> Self::Stmt;
855
856 /// A counted loop from zero to `bound`, exclusive.
857 ///
858 /// Given rather than assembled from the pieces because the counter's
859 /// type is the target's business: C declares `size_t`, Java `int`,
860 /// and a walk that wrote the declaration itself would be choosing
861 /// for both.
862 fn count_up(counter: &str, bound: Self::Expr, body: Self::Block) -> Self::Stmt;
863
864 /// A loop with no counter.
865 ///
866 /// A `@mutable` aggregate's headers run until the delimiter is used
867 /// up and a PL_CDR1 list until its sentinel; neither has a bound,
868 /// and a counted loop with the clauses left empty would claim an
869 /// induction variable somebody forgot.
870 fn until(condition: Self::Expr, body: Self::Block) -> Self::Stmt;
871
872 /// Leave the enclosing loop.
873 fn leave() -> Self::Stmt;
874
875 /// A comment on its own line.
876 fn note(doc: Doc) -> Self::Stmt;
877
878 /// A value read for its effect and not kept.
879 ///
880 /// `(void)zdw_read_dheader(r);` in C, where an ignored return is a
881 /// `-Wunused-result` away from a warning and the cast is how a
882 /// caller says it meant it. A target where a call is already a
883 /// statement writes the call: `(void) expr;` is not a Java
884 /// statement at all.
885 fn drop_value(value: Self::Expr) -> Self::Stmt {
886 Self::perform(Self::cast(Self::nothing(), value))
887 }
888
889 /// How this target says a parameter is deliberately not read.
890 ///
891 /// Most decoders have nothing to do with the try-construct
892 /// behaviour — a `long` has no bound to miss — and the C probe is
893 /// built with `-Wunused-parameter -Werror`, where that fails the
894 /// build. `(void)zd_tc;` is C's way of saying so. A target whose
895 /// compiler does not warn on an unread parameter answers with no
896 /// statement, which is a legitimate answer and not an omission:
897 /// `javac` has no such warning, and `zd_tc;` is not a Java
898 /// statement at all.
899 fn mark_used(name: &str) -> Vec<Self::Stmt> {
900 vec![Self::perform(Self::cast(Self::nothing(), Self::name(name)))]
901 }
902
903 /// `zd_dec_<T>(r, <destination>, <behaviour>);` — one nested
904 /// decoder run, with its answer put where `place` says.
905 ///
906 /// The read direction's counterpart of [`Handoff`], and a whole
907 /// method rather than an argument form because the two targets do
908 /// not put the destination in the same syntactic position. C passes
909 /// its address and the callee writes through it. Java has no
910 /// address to pass and no out-parameter to pass it in, so the
911 /// decoder answers with the value and the destination is the
912 /// assignment's left-hand side — the same decision, one step later.
913 ///
914 /// A backend that overrides this is saying its decoders return
915 /// their value; [`CodecTarget::routine`] has to agree with it, and
916 /// so does [`CodecTarget::give_back`].
917 fn read_call(
918 callee: &str,
919 place: Self::Expr,
920 form: Handoff,
921 behaviour: Self::Expr,
922 ) -> Self::Stmt {
923 Self::perform(Self::call(
924 callee,
925 vec![Self::name("r"), Self::handoff(place, form), behaviour],
926 ))
927 }
928
929 /// The whole sample handed straight on to another type's decoder.
930 ///
931 /// What a `typedef` reads as: transparent on the wire, transparent
932 /// to §7.2.2.7, and in C transparent in storage too — the
933 /// destination and the behaviour go through untouched, and neither
934 /// an address nor a dereference is taken because `v` is already the
935 /// one the caller gave. Separate from [`CodecTarget::read_call`]
936 /// because there is no `place` here to take: the destination is the
937 /// parameter itself.
938 fn read_forward(callee: &str) -> Self::Stmt {
939 Self::perform(Self::call(
940 callee,
941 vec![Self::name("r"), Self::name("v"), Self::name("zd_tc")],
942 ))
943 }
944
945 // -- assembly -----------------------------------------------------
946
947 /// Room for `count` values of `element`, with `fill` inside it.
948 ///
949 /// The one operation in the reader that is not a spelling. C sets
950 /// the length, nulls the buffer, allocates `count * sizeof(element)`
951 /// out of the reader's arena, returns if that fails, and runs the
952 /// fill inside the guard — five statements. A target whose
953 /// allocation throws rather than returning nothing writes one and
954 /// has no guard to put the fill in.
955 ///
956 /// `base` is the local the codec holds the sequence in, not a
957 /// finished expression: how a target reaches into it is the
958 /// target's own — C's sequence keeps a `_length` and a `_buffer`
959 /// behind a pointer, and a language with a list type has neither.
960 /// `fill` is the statements that fill it, already built; a backend
961 /// may drop the guard but must not drop the fill.
962 ///
963 /// Shaped from the C column, which is the only caller so far. A
964 /// second reader backend is what will say whether the shape holds —
965 /// a target that appends rather than writing into a slot will want
966 /// the element's place from here too, and it is better to widen this
967 /// when that caller exists than to guess at it now.
968 /// `assigned_whole` says what `fill` does with each slot: assign it
969 /// outright, as a sequence's element decoder does, or write into it,
970 /// as a `map`'s two halves do. It is the difference between a
971 /// target whose slot *is* storage and one whose slot is a reference
972 /// that has to point at some — C ignores it and Java does not.
973 fn make_room(
974 base: &str,
975 element: Self::Type,
976 count: Self::Expr,
977 assigned_whole: bool,
978 fill: Vec<Self::Stmt>,
979 ) -> Vec<Self::Stmt>;
980
981 /// [`CodecTarget::make_room`] for a sequence whose element is
982 /// `element_id`. A target that cannot spell a slot's start value from
983 /// `element` alone overrides it: Kotlin's `Array(n) { T() }` has no
984 /// constructor to call where `T` is a `typealias` of `Int`.
985 fn make_room_of(
986 _ast: &Ast,
987 _element_id: Id<TypeNode>,
988 base: &str,
989 element: Self::Type,
990 count: Self::Expr,
991 assigned_whole: bool,
992 fill: Vec<Self::Stmt>,
993 ) -> Vec<Self::Stmt> {
994 Self::make_room(base, element, count, assigned_whole, fill)
995 }
996
997 /// One scalar, as it comes off the runtime, into a place of
998 /// `type_`.
999 ///
1000 /// C's runtime hands back the member's own width and the assignment
1001 /// is the whole of it. Java has no unsigned type and no integer
1002 /// narrower than `int` in an expression: `readI8` answers an `int`
1003 /// because that is what a number in [-128, 127] is once it has been
1004 /// widened, and a `byte` field does not take one without being told
1005 /// to. Which of its holders need that is the backend's, and a walk
1006 /// that cast unconditionally would put a redundant cast in the C
1007 /// column, where `-Wall` names it.
1008 fn store(place: Self::Expr, type_: Self::Type, value: Self::Expr) -> Self::Stmt {
1009 let _ = type_;
1010 Self::perform(Self::binary("=", place, value))
1011 }
1012
1013 /// A local the body writes *into* rather than assigns.
1014 ///
1015 /// A C local of a struct type is storage the moment it is declared.
1016 /// A Java local of a class type is a reference to none, and a body
1017 /// that assigns its fields would be dereferencing a null. Separate
1018 /// from [`CodecTarget::declare`], which is for a local the body
1019 /// assigns before it reads — those need no storage of their own and
1020 /// a target that made some would be allocating for nothing.
1021 fn storage(type_: Self::Type, name: &str) -> Self::Stmt {
1022 Self::declare(type_, name, None)
1023 }
1024
1025 /// One field of a header the runtime handed back.
1026 ///
1027 /// `zd_h.member_id` in C. A target whose runtime publishes its
1028 /// headers behind accessors answers with the call — `zd_h.memberId()`
1029 /// — and the walk names the field rather than the spelling. Not
1030 /// [`CodecTarget::field_of`], which reaches into a value of a
1031 /// *generated* type, where both columns write a field.
1032 fn header_field(value: Self::Expr, field: &str) -> Self::Expr {
1033 Self::field_of(value, field)
1034 }
1035
1036 /// Where a **sequence**'s element goes while it is being read, and
1037 /// what it takes to get it there: the statements before, the place,
1038 /// and the statements after.
1039 ///
1040 /// Almost always `(nothing, base[index], nothing)`, and it is a seam
1041 /// because of one container. `std::vector<bool>` is specialised —
1042 /// [vector.bool] — and its `operator[]` returns a proxy object, not a
1043 /// `bool &`. A codec that takes its destination by reference has
1044 /// nowhere to point, so the C++ column reads into a local and assigns
1045 /// it back. Every other element of every other target is a real
1046 /// object and answers with the subscript.
1047 ///
1048 /// Only a sequence needs it. `T a[2]` is a `std::array`, which is not
1049 /// specialised for any element, and a `map`'s halves are a
1050 /// `std::pair`'s members, which are real objects too.
1051 ///
1052 /// The write direction has no such problem: reading a proxy is what
1053 /// a proxy is for, and `write_bool(v[i])` converts.
1054 fn sequence_element(
1055 base: &str,
1056 index: Self::Expr,
1057 element: Self::Type,
1058 ) -> (Vec<Self::Stmt>, Self::Expr, Vec<Self::Stmt>);
1059
1060 /// Keep the first `count` elements of the collection in `base` and
1061 /// drop the rest.
1062 ///
1063 /// §7.3.1.2.1.12.3's TRIM for a bounded collection, which the reader
1064 /// applies after every element has been read: stopping at the bound
1065 /// would leave the rest of the sequence in the stream and put every
1066 /// following member at the wrong offset.
1067 ///
1068 /// A statement and not an assignment to [`CodecTarget::count_of`],
1069 /// which is what this used to be. C's sequence is a `_length` beside
1070 /// a `_buffer` and the length *is* the answer — the elements past
1071 /// the bound stay in the buffer, unreferenced. A `std::vector` knows
1072 /// its own size and `v.size() = 5u` is not an assignment; a
1073 /// `List` shortens differently again. The walk says how many to
1074 /// keep and the target says how.
1075 fn keep_first(base: &str, count: Self::Expr) -> Vec<Self::Stmt>;
1076
1077 /// Whether an `@optional` member is present.
1078 ///
1079 /// The last structural assumption C made and no other target
1080 /// shares. C declares a `boolean _has_m` beside the member and this
1081 /// reads it; `adapter/java.rs` declares one boxed field and lets
1082 /// `null` mean absent, "because a `boolean _has_m` and an `m` are
1083 /// two things that can disagree, and nothing in the type system
1084 /// stops them". A backend that inherited C's flag would reach a
1085 /// field the materialiser never emitted.
1086 fn present(base: &str, field: &str) -> Self::Expr;
1087
1088 /// Say that an `@optional` member is present, or is not.
1089 ///
1090 /// C assigns the parallel flag. A target where absence is the value
1091 /// assigns nothing on the way in — writing the member is what makes
1092 /// it present — and assigns [`CodecTarget::absent`] on the way out.
1093 /// Returning no statement is a legitimate answer.
1094 fn set_present(base: &str, field: &str, present: bool) -> Vec<Self::Stmt>;
1095
1096 /// Statements that leave every case member of a union unset, before
1097 /// the member the discriminator selects is read.
1098 ///
1099 /// The decoder starts from the union's Table 9 default, which carries
1100 /// the member the default discriminator selects. A column whose union
1101 /// has a "not set" for a case field — Python's and Julia's `None` and
1102 /// `nothing`, Swift's `nil`, Nim's and OCaml's `Option`, a missing
1103 /// key or property in Lua, Node and TypeScript, Elixir's `nil` — and
1104 /// promises that at most one case is live has to clear that member,
1105 /// or a decoded sample carries the default branch's value beside the
1106 /// one read (measured: `idl-conformance/_interop` `mapenum`, a `Sel`
1107 /// read with `_d = 2` came back with `n = 9` *and* `z = 0`). Nothing
1108 /// by default: in C the cases share storage, and where a case field
1109 /// has no "not set" (C++, C#, Java, Go, D, Zig, F#, Ada, Kotlin) its
1110 /// default value is what an unselected field holds anyway.
1111 fn clear_union_cases(base: &str, fields: &[String]) -> Vec<Self::Stmt> {
1112 let _ = (base, fields);
1113 Vec::new()
1114 }
1115
1116 /// Statements that make an array's Table 9 default writable, before
1117 /// its elements are read into it; `lengths` are its dimensions,
1118 /// outermost first.
1119 ///
1120 /// Nothing by default: every column but one holds an array in
1121 /// something its elements can be assigned into. Python declares the
1122 /// member `tuple[T, …]` (`adapter/python.rs`) and a tuple cannot be
1123 /// written into, so its default is a tuple and the reader turns it
1124 /// into lists here and back in [`CodecTarget::close_array`].
1125 fn open_array(base: &str, lengths: &[i128]) -> Vec<Self::Stmt> {
1126 let _ = (base, lengths);
1127 Vec::new()
1128 }
1129
1130 /// Statements that give a read array the type its member declares,
1131 /// after its last element is read. Nothing by default; see
1132 /// [`CodecTarget::open_array`].
1133 fn close_array(base: &str, lengths: &[i128]) -> Vec<Self::Stmt> {
1134 let _ = (base, lengths);
1135 Vec::new()
1136 }
1137
1138 /// A union's selected case member.
1139 ///
1140 /// C declares the cases in an anonymous `union _u` inside the
1141 /// struct, so a member is `v->_u.x`; `adapter/java.rs` declares one
1142 /// field per case directly on the class, so it is `v.x`.
1143 fn case_member(base: &str, field: &str) -> Self::Expr;
1144
1145 /// A union's discriminant.
1146 ///
1147 /// `_d` in every column whose materialiser names it that, which is
1148 /// all of them but one: DDS-TS 1.0 §7.5 names the property
1149 /// `discriminator`, and the TypeScript materialiser declares it
1150 /// under that name. The default is the name the other columns
1151 /// share, so a column that does not override generates exactly what
1152 /// it generated before this seam existed.
1153 fn discriminant(base: &str) -> Self::Expr {
1154 Self::member(base, "_d")
1155 }
1156
1157 /// The integer an enumeration's value carries, given the value.
1158 ///
1159 /// The identity in every column but one, and deliberately so: an
1160 /// enumeration is a numeric constant at the language surface there,
1161 /// so the value in the variable *is* the integer — under
1162 /// [`zerodds_ast_layout::EnumeratorValue::Declared`] it is even the
1163 /// integer that goes on the wire, which is why
1164 /// [`encode::enumeration_write`] emits no chain for that setting.
1165 ///
1166 /// DDS-TS 1.0 §7.6 makes an enumeration a string-literal union, so
1167 /// that column has no integer in the variable at all and answers
1168 /// with a lookup in the generated `<Enum>Ordinal` table. Measured:
1169 /// without this seam `enum-sparse-values` wrote `70 79 87` — the
1170 /// first code unit of each enumerator's name — where the ledger has
1171 /// `65535 1 100`.
1172 ///
1173 /// [`encode::enumeration_write`]: crate::emit::encode
1174 fn enumerator_ordinal(ast: &Ast, id: Id<TypeNode>, value: Self::Expr) -> Self::Expr {
1175 let _ = (ast, id);
1176 value
1177 }
1178
1179 /// The value a union case label is compared against, in this
1180 /// target's spelling of the discriminator's type.
1181 ///
1182 /// The default is the label folded to an integer and narrowed to the
1183 /// discriminator's own type, which is what every column with a
1184 /// numeric enumeration needs. A column whose enumeration is not
1185 /// numeric at the language surface — DDS-TS 1.0 §7.6 makes it a
1186 /// string-literal union — cannot compare a discriminant against a
1187 /// number at all, and answers with its own constant.
1188 fn case_label(ast: &Ast, discriminator: Id<TypeNode>, value: i128) -> Self::Expr {
1189 Self::cast(Self::value_type(ast, discriminator), Self::wide_int(value))
1190 }
1191
1192 /// One half of a `map`'s entry.
1193 ///
1194 /// C generates a struct with `key` and `value`; C++'s entry is a
1195 /// `std::pair`, whose halves are `first` and `second`. The walk asks
1196 /// for the half and the backend names it.
1197 fn entry_half(entry: Self::Expr, key: bool) -> Self::Expr;
1198
1199 /// The place a member is reached at, given that the column could
1200 /// not put the member's value inside its own aggregate.
1201 ///
1202 /// [`zerodds_ast_layout::member_held_inline_closes_a_cycle`] is what
1203 /// says so: the member's type reaches the aggregate that declares it
1204 /// over nothing but inline edges, so a column whose member *is* the
1205 /// value has no size for it. `place` is the place the seams below
1206 /// would have reached — the member, the `@optional` accessor, the
1207 /// `@external` one, or a union branch — and the answer is that place
1208 /// as the column keeps it once the indirection is there.
1209 ///
1210 /// The default is the place unchanged, and it is the right answer
1211 /// for fifteen of the seventeen columns: a member that is already a
1212 /// reference — Python's `Optional[T]`, Rust's `Option<Box<T>>`,
1213 /// Java's boxed field — has a size whatever it points at, so nothing
1214 /// about it changes when it closes a cycle. C is the column that
1215 /// asks: `T next;` beside a `_has_next` flag is storage that
1216 /// contains itself, and the member has to become `T *next`.
1217 ///
1218 /// Not folded into [`CodecTarget::optional_value`]: that seam is
1219 /// handed the member's type and not the aggregate that declares it,
1220 /// and the cycle is a fact about the pair. The call sites hold both
1221 /// and ask this only where the pair closes one, so a column that
1222 /// takes the default emits exactly the source it emitted before this
1223 /// seam existed.
1224 fn behind_indirection(ast: &Ast, id: Id<TypeNode>, place: Self::Expr) -> Self::Expr {
1225 let _ = (ast, id);
1226 place
1227 }
1228
1229 /// The reading counterpart of [`CodecTarget::behind_indirection`]:
1230 /// the same place, and what has to happen before a reader can write
1231 /// through it.
1232 ///
1233 /// A column that keeps an indirection has nothing to write into
1234 /// until the indirection points somewhere, so this is where the
1235 /// storage is made. `id` is the member's type, which is what an
1236 /// allocation needs to know the size of.
1237 ///
1238 /// Returns the statements that make room, followed by the place, in
1239 /// the shape [`CodecTarget::optional_destination`] uses and for the
1240 /// same reason.
1241 fn make_indirection(
1242 ast: &Ast,
1243 id: Id<TypeNode>,
1244 place: Self::Expr,
1245 ) -> (Vec<Self::Stmt>, Self::Expr) {
1246 let _ = (ast, id);
1247 (Vec::new(), place)
1248 }
1249
1250 /// Where a *reader* puts an `@optional` member's value, and what it
1251 /// takes to have somewhere to put it.
1252 ///
1253 /// The write direction has [`CodecTarget::optional_value`], which
1254 /// reaches a value that is already there. Reading is the other way
1255 /// round and the two targets differ in a way no expression covers:
1256 /// C's optional member is a plain `T` beside a `_has_` flag, so the
1257 /// destination is the member and nothing has to happen first; C++'s
1258 /// is a `std::optional<T>` and the member's decoder takes a `T&`, so
1259 /// the optional has to be *engaged* before there is a `T` to write
1260 /// into at all.
1261 ///
1262 /// Returns the statements that make room, followed by the place. The
1263 /// statements go immediately before the read; a backend that needs
1264 /// none returns an empty vector, and the generated source is then
1265 /// exactly what it was before this seam existed.
1266 ///
1267 /// Not folded into [`CodecTarget::set_present`]: that is called
1268 /// *after* the read at two of the five sites and before it at the
1269 /// other three, and moving it would reorder the C column's output
1270 /// for a difference that is not C's.
1271 fn optional_destination(base: &str, field: &str) -> (Vec<Self::Stmt>, Self::Expr);
1272
1273 /// The value an `@optional` member holds, given that it holds one.
1274 ///
1275 /// C's optional member is a plain `T` beside a `_has_` flag, so this
1276 /// is the member. C++'s is a `std::optional<T>` and the codec takes
1277 /// a `T`, so it is `.value()`. Java's boxed field is already the
1278 /// value.
1279 ///
1280 /// The member's type is a parameter because one target's answer
1281 /// depends on it and no other's does. `adapter/csharp.rs` wraps a
1282 /// *value* type in `Nullable<T>` and leaves a reference type alone,
1283 /// so the unwrapping is `.Value` for the first and nothing for the
1284 /// second — and `string?.Value` does not compile any more than
1285 /// passing an `int?` where an `int` is wanted does. A backend that
1286 /// answered from the name alone would be right for one half of its
1287 /// own mapping.
1288 ///
1289 /// Only ever reached inside a branch the presence test guarded, so a
1290 /// target whose accessor throws on an absent value is safe here.
1291 fn optional_value(ast: &Ast, id: Id<TypeNode>, base: &str, field: &str) -> Self::Expr;
1292
1293 /// Whether this column holds an `@optional` or `@external` member of
1294 /// `aggregate`, of type `member_type`, behind a reference of its own
1295 /// — where the shared walk would reach it through
1296 /// [`CodecTarget::present`], [`CodecTarget::optional_value`] and
1297 /// their `external_` counterparts. A member this answers for goes
1298 /// through [`CodecTarget::reference_present`],
1299 /// [`CodecTarget::reference_value`],
1300 /// [`CodecTarget::reference_destination`] and
1301 /// [`CodecTarget::reference_absent`] instead.
1302 ///
1303 /// `false` by default, which leaves every column's output as it
1304 /// was. Ada answers from its adapter
1305 /// (`crate::adapter::ada::held_by_reference`): a member whose type is
1306 /// still incomplete where the aggregate is declared is an access
1307 /// type there (RM 3.10.1), which depends on the order Ada declares
1308 /// in and on nothing the layout plans. The generated Ada for
1309 /// `recursive-optional-external` asked such a member for `Present`
1310 /// and did not compile (44 matrix cells, 2026-10-01).
1311 fn holds_by_reference(
1312 ast: &Ast,
1313 aggregate: Id<TypeNode>,
1314 member_type: Id<TypeNode>,
1315 optional: bool,
1316 external: bool,
1317 ) -> bool {
1318 let _ = (ast, aggregate, member_type, optional, external);
1319 false
1320 }
1321
1322 /// Whether a member [`CodecTarget::holds_by_reference`] answers for
1323 /// is present. Reached only where that answered `true`.
1324 fn reference_present(base: &str, field: &str) -> Self::Expr {
1325 Self::present(base, field)
1326 }
1327
1328 /// The value behind such a member, given that it is present.
1329 fn reference_value(ast: &Ast, id: Id<TypeNode>, base: &str, field: &str) -> Self::Expr {
1330 Self::optional_value(ast, id, base, field)
1331 }
1332
1333 /// Where a reader puts such a member's value, and what it takes to
1334 /// have somewhere to put it; `id` is the member's type.
1335 fn reference_destination(
1336 ast: &Ast,
1337 id: Id<TypeNode>,
1338 base: &str,
1339 field: &str,
1340 ) -> (Vec<Self::Stmt>, Self::Expr) {
1341 let _ = (ast, id);
1342 Self::optional_destination(base, field)
1343 }
1344
1345 /// The statements that leave such a member absent.
1346 fn reference_absent(base: &str, field: &str) -> Vec<Self::Stmt> {
1347 Self::set_present(base, field, false)
1348 }
1349
1350 /// Whether this column keeps a member that closes a type cycle the
1351 /// way it keeps an `@external` one — through the `external_` seams
1352 /// below, whether or not the member carries the annotation.
1353 ///
1354 /// `false` by default, which leaves a column's output as it was. The
1355 /// columns that answer `true` are the ones whose `@external` member
1356 /// is an indirection of their own (C++'s `std::shared_ptr`, D's and
1357 /// Zig's pointer, Nim's `ref`, Swift's `ZdExternal`) and whose plain
1358 /// member is the value, so a member on a cycle needs the same holder;
1359 /// [`crate::adapter::held_externally`] is the rule, and their adapters
1360 /// and defaulters ask it too. Ada decides by its declaration order
1361 /// ([`CodecTarget::holds_by_reference`]) and C by the pointer it declares
1362 /// (`behind_pointer`), so neither takes this.
1363 fn holds_a_cycle_externally() -> bool {
1364 false
1365 }
1366
1367 /// Whether this column holds an `@external` member behind a pointer
1368 /// of its own, where the member's value is not a pointer already.
1369 ///
1370 /// XTypes 1.3 §7.5.1.2.3.1 maps an external member of C to
1371 /// "pointer-to-X". The member is then reached through
1372 /// [`CodecTarget::behind_indirection`] and a reader makes storage for
1373 /// it with [`CodecTarget::make_indirection`], the way both do for a
1374 /// member that closes a cycle (`behind_pointer` asks both questions
1375 /// at once). `false` by default; the columns that keep an `@external`
1376 /// member behind a holder of their own go through the `external_`
1377 /// seams instead.
1378 fn externals_are_pointers() -> bool {
1379 false
1380 }
1381
1382 /// Whether this column holds an `@optional` member behind a pointer
1383 /// of its own, where the member's value is not a pointer already.
1384 ///
1385 /// XTypes 1.3 §7.5.1.2.4.1 maps an optional member of C to
1386 /// "pointer-to-X" and says "A NULL pointer shall indicate an omitted
1387 /// value"; §7.5.1.2.4.2 does the same for C++. The member is then
1388 /// reached through [`CodecTarget::behind_indirection`] and a reader
1389 /// makes storage for it with [`CodecTarget::make_indirection`],
1390 /// exactly as for an `@external` one; [`CodecTarget::present`] asks
1391 /// whether the pointer is not NULL. `false` by default: every other
1392 /// column holds an absent value in its own way.
1393 fn optionals_are_pointers() -> bool {
1394 false
1395 }
1396
1397 /// Whether the column's value of `id` is a pointer already, so that
1398 /// an `@external` or `@optional` member of that type needs no pointer
1399 /// of its own. C's `string`, `wstring` and `fixed` are
1400 /// `const char *` (XTypes 1.3 §7.5.1.2.3.1); a C++ `std::string` is
1401 /// a value.
1402 fn value_is_pointer(ast: &Ast, id: Id<TypeNode>) -> bool {
1403 crate::adapter::c::value_is_pointer(ast, id)
1404 }
1405
1406 /// Statements that stop a writer from going on when an `@external`
1407 /// member that is not `@optional` has no value at the time of the
1408 /// write.
1409 ///
1410 /// XTypes 1.3 §7.5.1.2.3.1.1 (C) and §7.5.1.2.3.2.1 (C++): "If the
1411 /// member is only external but not optional, then it is not allowed
1412 /// for the member to be NULL at the time of a write." `present` says
1413 /// whether the member has a value. Nothing by default: a column that
1414 /// holds an external member inline has no unset state to refuse.
1415 fn refuse_unset_external(present: Self::Expr) -> Vec<Self::Stmt> {
1416 let _ = present;
1417 Vec::new()
1418 }
1419
1420 /// The value of an `@external` member, as the member's codec takes
1421 /// it.
1422 ///
1423 /// DDS-XTypes 1.3 Table 21: `@external` changes no octet — it says
1424 /// where the binding keeps the value. Most columns keep it inline,
1425 /// and the default is then exactly what the walk wrote before this
1426 /// seam existed: the member, or the optional accessor where the
1427 /// member is also `@optional`. A column that keeps it behind an
1428 /// indirection — D's `T*`, which is also what gives a
1429 /// self-recursive type a size at all — answers with the
1430 /// dereference, for both. A walk that reached the member directly
1431 /// would hand a pointer to a codec that takes the value.
1432 fn external_value(
1433 ast: &Ast,
1434 id: Id<TypeNode>,
1435 base: &str,
1436 field: &str,
1437 optional: bool,
1438 ) -> Self::Expr {
1439 if optional {
1440 return Self::optional_value(ast, id, base, field);
1441 }
1442 Self::member(base, field)
1443 }
1444
1445 /// Where a *reader* puts an `@external` member's value, and what it
1446 /// takes to have somewhere to put it. The reading counterpart of
1447 /// [`CodecTarget::external_value`], shaped like
1448 /// [`CodecTarget::optional_destination`] and for the same reason: a
1449 /// column whose member is an indirection has to make storage before
1450 /// there is a value to write into. The default is the walk's old
1451 /// answer for both combinations.
1452 fn external_destination(
1453 base: &str,
1454 field: &str,
1455 optional: bool,
1456 ) -> (Vec<Self::Stmt>, Self::Expr) {
1457 if optional {
1458 return Self::optional_destination(base, field);
1459 }
1460 (Vec::new(), Self::member(base, field))
1461 }
1462
1463 /// Whether an `@optional @external` member is present.
1464 ///
1465 /// Separate from [`CodecTarget::present`] because the two
1466 /// annotations can answer differently in one column: D's plain
1467 /// `@optional` is `Nullable!(T)` and its `isNull`, while
1468 /// `@optional @external` is one pointer and its `null` — a wrapper
1469 /// over the pointer would carry a present-but-null state the wire
1470 /// form has no encoding for.
1471 fn external_present(base: &str, field: &str) -> Self::Expr {
1472 Self::present(base, field)
1473 }
1474
1475 /// Say that an `@optional @external` member is present, or is not.
1476 /// The counterpart of [`CodecTarget::set_present`], split for the
1477 /// reason [`CodecTarget::external_present`] gives.
1478 fn external_set_present(base: &str, field: &str, present: bool) -> Vec<Self::Stmt> {
1479 Self::set_present(base, field, present)
1480 }
1481
1482 /// The value of an `@external` union branch, as the branch's codec
1483 /// takes it. The union counterpart of
1484 /// [`CodecTarget::external_value`], reached through
1485 /// [`CodecTarget::case_member`] because that is the accessor a
1486 /// branch has; there is no `optional` parameter because no union
1487 /// member is `@optional` (DDS-XTypes 1.3 §7.2.2.4.4.4.7). The
1488 /// default is the accessor itself — the columns that keep the value
1489 /// inline — and a column that keeps an indirection answers with the
1490 /// dereference, exactly as it does for a structure member.
1491 fn external_case_value(ast: &Ast, id: Id<TypeNode>, base: &str, field: &str) -> Self::Expr {
1492 let _ = (ast, id);
1493 Self::case_member(base, field)
1494 }
1495
1496 /// Where a *reader* puts an `@external` union branch's value, and
1497 /// what it takes to have somewhere to put it. The union counterpart
1498 /// of [`CodecTarget::external_destination`], shaped the same way
1499 /// and defaulted the same way.
1500 fn external_case_destination(base: &str, field: &str) -> (Vec<Self::Stmt>, Self::Expr) {
1501 (Vec::new(), Self::case_member(base, field))
1502 }
1503
1504 /// Where a sequence keeps the elements themselves.
1505 ///
1506 /// C's sequence is a `_length` and a `_buffer` and this is the
1507 /// buffer; a target whose sequence *is* the array answers the value.
1508 fn elements(base: &str) -> Self::Expr;
1509
1510 /// Whether a value the reader had to materialise is there at all.
1511 ///
1512 /// C's `zdw_read_string_alloc` answers `NULL` where the reader's
1513 /// heap is exhausted, and every use of what it produced is guarded
1514 /// against that — a decoder that measured a null string would read
1515 /// through the pointer. A target whose string type owns its own
1516 /// storage has no such state and answers `None`, and the guard is
1517 /// then not written at all rather than written against a value that
1518 /// is always there: `std::string != std::nullopt` is not an
1519 /// expression.
1520 ///
1521 /// Distinct from [`CodecTarget::present`], which asks about an
1522 /// `@optional` member — a property of the sample. This asks whether
1523 /// the *read* produced anything, which is a property of the runtime.
1524 fn materialised(value: Self::Expr) -> Option<Self::Expr>;
1525
1526 /// The value that says a target holds nothing.
1527 ///
1528 /// `NULL` in C, `null` in Java and C#, `None` in Python. What a
1529 /// decoder writes into a pointer before it has allocated, and what
1530 /// it compares against to find out whether the allocation worked.
1531 fn absent() -> Self::Expr;
1532
1533 /// Statements gathered into a body.
1534 fn block(statements: Vec<Self::Stmt>) -> Self::Block;
1535
1536 /// A body taken apart again.
1537 ///
1538 /// A walk that has built a nested block and then finds it belongs
1539 /// inline — an array's innermost loop under a `DHEADER`, a union's
1540 /// if-chain after the last arm — would otherwise have to carry a
1541 /// `Vec` and a `Block` in parallel and keep them agreeing.
1542 fn into_statements(block: Self::Block) -> Vec<Self::Stmt>;
1543
1544 /// One generated name, in the spelling this target publishes it
1545 /// under.
1546 ///
1547 /// The identity everywhere but Go, and it exists because Go has no
1548 /// visibility keyword: an identifier is exported if and only if its
1549 /// first character is upper case (Go specification, "Exported
1550 /// identifiers"), so a name is the only place a Go column can put
1551 /// the decision. Applied inside
1552 /// [`crate::emit::encode::codec_name`] and
1553 /// [`crate::emit::decode::decoder_name`], which is what makes it a
1554 /// single decision: every caller of either — the walk's own nested
1555 /// calls, `wireprobe-zerodds-go-gen`'s entry point, a test naming a
1556 /// function — gets the published spelling without asking.
1557 ///
1558 /// A target that overrides this owes the transform an inverse in
1559 /// name only: it must be injective, or two codecs collide under one
1560 /// name and no compiler reports that as a collision.
1561 fn published_name(name: String) -> String {
1562 name
1563 }
1564
1565 /// A codec function or method.
1566 ///
1567 /// `body` absent asks for a forward declaration, which C needs ahead
1568 /// of the bodies so a recursive or mutually recursive type does not
1569 /// depend on the order the walk visited its types in. A target that
1570 /// has no such thing returns `None` and the caller emits nothing.
1571 ///
1572 /// [`Routine::tolerate_unused`] marks a codec that may legitimately
1573 /// have no caller. Generated code is compiled with an uncalled
1574 /// function as an error, and that is worth keeping: normally it
1575 /// means the generator emitted the wrong thing. The exception is a
1576 /// codec reached from one position and written inline at another,
1577 /// where having a caller is a property of the dialect rather than
1578 /// of the type graph.
1579 fn routine(spec: Routine<Self>) -> Option<Self::Decl>;
1580
1581 /// A new reader entry with an explicit, target-derived initializer.
1582 /// Targets that cannot preserve this contract refuse the new entry only;
1583 /// existing data routines and their initialization remain unchanged.
1584 fn initialized_reader(_spec: Routine<Self>, _initializer: Self::Stmt) -> Option<Self::Decl> {
1585 None
1586 }
1587
1588 // -- the materialiser's boundary ----------------------------------
1589
1590 /// How this target spells the type of `id` where a codec holds it.
1591 ///
1592 /// The materialiser's answer, not the walk's: a `sequence<long>` is
1593 /// a struct with a buffer and a length in C, a `std::vector` in C++
1594 /// and a `List<Integer>` in Java, and only the target knows which.
1595 fn value_type(ast: &Ast, id: Id<TypeNode>) -> Self::Type;
1596
1597 /// The 64-bit pattern a bitmask arrives as, in the bitmask's own
1598 /// declared type.
1599 ///
1600 /// One cast to [`Self::value_type`] by default, which every target
1601 /// with a real conversion operator answers with — `(Flags)zd_bits`
1602 /// in C, `(int) zd_bits` in Java. TypeScript overrides it: its
1603 /// declared bitmask type is an alias whose holder is a `number` up
1604 /// to 32 bits, its `as` is erased and refuses `bigint` to `number`
1605 /// outright (TS2352), so the narrowing has to be a runtime
1606 /// conversion through the holder before the alias names it.
1607 fn bitmask_value(ast: &Ast, id: Id<TypeNode>, value: Self::Expr) -> Self::Expr {
1608 Self::cast(Self::value_type(ast, id), value)
1609 }
1610
1611 /// Table 9's value for `id`, written into `place`.
1612 ///
1613 /// What a reader puts where a member the stream did not carry
1614 /// belongs — DDS-XTypes 1.3 Table 9. The value is the
1615 /// specification's and the same in every column; what differs is
1616 /// where a target keeps it. C calls a generated defaulter because a
1617 /// struct-valued default is not an expression it can assign; a
1618 /// target with literals for its aggregates assigns one.
1619 ///
1620 /// # Errors
1621 /// When the target has no form for Table 9's value. Fallible
1622 /// because a backend under construction has to be able to refuse
1623 /// here: a reader generated with a hole in it and no error is a
1624 /// reader that fails where it runs rather than where it was built,
1625 /// and the two are a long way apart.
1626 fn default_into(
1627 ast: &Ast,
1628 id: Id<TypeNode>,
1629 place: Self::Expr,
1630 ) -> Result<Self::Stmt, EncodeError>;
1631
1632 /// The declared `@default` of `node`, written into `place`, or
1633 /// Table 9's value where the member declares none.
1634 ///
1635 /// Fallible on purpose: a `@default` a target cannot spell is
1636 /// reported rather than quietly replaced by Table 9's zero, which
1637 /// is a different sample from the one the designer declared.
1638 ///
1639 /// # Errors
1640 /// When the declared default has no form in this target.
1641 fn member_default(
1642 ast: &Ast,
1643 node: &MemberNode,
1644 type_: Id<TypeNode>,
1645 place: Self::Expr,
1646 ) -> Result<Vec<Self::Stmt>, EncodeError>;
1647
1648 /// An IDL identifier as this target spells a member or a field.
1649 ///
1650 /// Every language escapes the identifiers that collide with its own
1651 /// keywords, and each escapes them differently — C prefixes, Java
1652 /// prefixes with a different mark, C# writes `@`. Both halves of a
1653 /// column have to agree: a member the materialiser declared escaped
1654 /// and the codec reaches unescaped is a field no compiler finds.
1655 fn escape(name: &str) -> String;
1656
1657 /// The path from a derived structure to the base members `depth` levels up,
1658 /// for a column that binds inheritance as a first member `parent`
1659 /// (XTypes 1.3 §7.5.1.4.1.3), ending in the separator: `parent.` for the
1660 /// base, `parent.parent.` for its base. `None`, the default, is a column
1661 /// that declares the base's members in the derived structure.
1662 fn inherited_path(depth: usize) -> Option<String> {
1663 let _ = depth;
1664 None
1665 }
1666
1667 /// The name this target gives a `map`'s entry type.
1668 ///
1669 /// A `map<K, V>` has no direct form in most targets and becomes a
1670 /// collection of a generated pair; what that pair is called is the
1671 /// materialiser's, and the codec has to call it the same thing.
1672 fn map_entry_name(ast: &Ast, id: Id<TypeNode>) -> String;
1673
1674 /// The type a `map`'s entry is *declared* with — the reader's
1675 /// scratch entry and `make_room`'s element.
1676 ///
1677 /// The default names [`CodecTarget::map_entry_name`], which is
1678 /// right wherever the materialiser declares a type of that name. It
1679 /// is not right in C++, whose materialiser spells the entry as
1680 /// `std::pair<K, V>` and declares nothing under the tag —
1681 /// `map_string_long zd_scratch;` was a name no compiler finds (20
1682 /// zerodds-cpp read cells across the map family).
1683 fn map_entry_type(ast: &Ast, id: Id<TypeNode>) -> Self::Type {
1684 Self::named(&Self::map_entry_name(ast, id))
1685 }
1686
1687 /// The constant one enumerator of `id` is named by.
1688 ///
1689 /// The materialiser's spelling. C flattens the modules into a
1690 /// prefix and writes a bare identifier; Java declares a class of
1691 /// `public static final int` and every use is written from the
1692 /// outer class down — `ZdTypes.common.Colour.BLUE`. A walk that
1693 /// built the name from [`CodecTarget::module_prefix`] alone would
1694 /// emit an unqualified `BLUE`, which in Java names nothing.
1695 ///
1696 /// The default is C's and not the general answer: it is
1697 /// [`CodecTarget::module_prefix`] and [`CodecTarget::escape`] put
1698 /// together, which works wherever an enumerator sits in the
1699 /// enclosing scope. C++ declares a *scoped* enumeration, so the
1700 /// same enumerator is `::a::b::Colour::red` and carries the type's
1701 /// own name in the middle, which no prefix supplies; that column
1702 /// overrides.
1703 fn enumerator_constant(ast: &Ast, id: Id<TypeNode>, enumerator: &str) -> Self::Expr {
1704 Self::name(&format!(
1705 "{}{}",
1706 Self::module_prefix(ast, id),
1707 Self::escape(enumerator)
1708 ))
1709 }
1710
1711 /// The prefix this target puts before a module's declarations.
1712 ///
1713 /// C has no namespaces and flattens `module a { module b { … } }`
1714 /// into a prefix; a target with namespaces answers the empty
1715 /// string and lets its own scoping do the work.
1716 fn module_prefix(ast: &Ast, id: Id<TypeNode>) -> String;
1717
1718 /// The name this target gives the type of `id`.
1719 ///
1720 /// What a generated codec function is named after, and the reason a
1721 /// codec name is built from this rather than from the IDL: two IDL
1722 /// types that collapse to one name in a target must not end up with
1723 /// one codec, and a target that escapes a keyword has to escape it
1724 /// the same way in both halves.
1725 fn type_name(ast: &Ast, id: Id<TypeNode>) -> String;
1726}