Skip to main content

zerodds_lang_rules/emit/
cpp.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2026 ZeroDDS Contributors
3//! The C++ backend of [`CodecTarget`].
4//!
5//! The third spelling of the same walk, and the closest to C's of the
6//! three — which makes the places it still differs the interesting ones:
7//!
8//! - **References, not pointers.** A codec takes `const T &v`, so
9//!   [`CodecTarget::member`] is `v.x` and [`CodecTarget::handoff`] hands
10//!   the value. C's `&v->x` has no counterpart here and neither does its
11//!   array decay: a `T[2][3]` is a `std::array<std::array<T, 3>, 2>`,
12//!   which is a value.
13//! - **Methods, not free functions.** `runtime/cpp` names them exactly
14//!   as the walk asks — `write_u32`, `finish_dheader` — so unlike Java
15//!   nothing is re-cased. A codec for a type is a `static` free function
16//!   in the probe's translation unit, which C++ does have.
17//! - **A `std::vector` knows its own size.** `count_of` is `.size()`
18//!   and `make_room` is `.resize(n)`; C's `_length` and `_buffer` have
19//!   no counterpart and neither does its allocation guard, because
20//!   `resize` throws rather than returning nothing.
21//! - **A plain pointer for `@optional`.** An absent member is `nullptr`
22//!   and presence is `!= nullptr` (XTypes 1.3 §7.5.1.2.4.2); the codec
23//!   reaches the value through [`CodecTarget::behind_indirection`] and a
24//!   reader makes it with [`CodecTarget::make_indirection`], the seams C
25//!   uses for the same member. There is no flag beside the value, for the
26//!   reason `adapter/java.rs` gives for Java: a flag beside the value is
27//!   two things that can disagree.
28//!
29//! What is *not* different is the wire. `runtime/cpp` implements
30//! DDS-XTypes 1.3 §7.4 from the clause and calls neither the Rust
31//! runtime nor the C one.
32
33use super::{BitPatternSource, CodecTarget, Constant, Handle, Handoff, Routine, Scalar};
34use crate::adapter::cpp::{
35    cpp_holder, cpp_primitive, cpp_type_path, cpp_type_tag, cpp_value_type, cxx_name,
36};
37use crate::encode::EncodeError;
38use zerodds_ast::{Ast, MemberNode, PrimitiveKind, TypeNode};
39use zerodds_ast_core::Id;
40use zerodds_lang_cst::common::{Doc, Ident, Literal, Path};
41use zerodds_lang_cst::cpp;
42
43/// The C++ backend.
44#[derive(Debug, Clone, Copy, PartialEq, Eq)]
45pub struct Cpp;
46
47impl Cpp {
48    /// A name in the runtime's namespace.
49    fn wire(name: &str) -> Path {
50        Path::new(&["zerodds", "wire", name])
51    }
52
53    /// `zerodds::wire::external_slot(place)` — the runtime function that
54    /// fills an `@external` member's `std::shared_ptr` and hands back a
55    /// reference to what it now points at. See
56    /// `runtime/cpp/include/zerodds/wire.hpp`.
57    fn external_slot(place: cpp::Expr) -> cpp::Expr {
58        cpp::Expr::Call {
59            callee: Box::new(cpp::Expr::Path(Self::wire("external_slot"))),
60            args: vec![place],
61        }
62    }
63}
64
65impl CodecTarget for Cpp {
66    type Expr = cpp::Expr;
67    type Stmt = cpp::Statement;
68    type Block = cpp::Block;
69    type Type = cpp::Type;
70    type Decl = cpp::Decl;
71
72    // -- types --------------------------------------------------------
73
74    /// The fixed-width types, from the materialiser's own table.
75    ///
76    /// `Index` and `Size` are both `std::size_t` here: C++ has an
77    /// unsigned type for each, so the distinction C needs — a `uint32_t`
78    /// counter against a `size_t` position — costs nothing to collapse
79    /// and a `std::vector::size()` returns the latter.
80    fn scalar(kind: Scalar) -> Self::Type {
81        let primitive = match kind {
82            Scalar::Index | Scalar::Size => {
83                return cpp::Type::named(Path::new(&["std", "size_t"]));
84            }
85            Scalar::Bool => PrimitiveKind::Boolean,
86            Scalar::Char => PrimitiveKind::Char,
87            Scalar::Int8 => PrimitiveKind::Int8,
88            Scalar::Int16 => PrimitiveKind::Int16,
89            Scalar::Int32 => PrimitiveKind::Int32,
90            Scalar::Int64 => PrimitiveKind::Int64,
91            Scalar::UInt8 => PrimitiveKind::UInt8,
92            Scalar::UInt16 => PrimitiveKind::UInt16,
93            Scalar::UInt32 => PrimitiveKind::UInt32,
94            Scalar::UInt64 => PrimitiveKind::UInt64,
95            Scalar::Float => PrimitiveKind::Float32,
96            Scalar::Double => PrimitiveKind::Float64,
97            Scalar::LongDouble => PrimitiveKind::Float128,
98        };
99        cpp_primitive(primitive)
100    }
101
102    fn named(name: &str) -> Self::Type {
103        cpp::Type::named(Path::new(&[name]))
104    }
105
106    fn nothing() -> Self::Type {
107        cpp::Type::Void
108    }
109
110    /// The handles are types of the runtime's namespace, not nested in
111    /// the writer: `zerodds::wire::DHeader`.
112    fn handle(kind: Handle) -> Self::Type {
113        cpp::Type::named(Self::wire(match kind {
114            Handle::DHeader => "DHeader",
115            Handle::EmHeader => "EmHeader",
116            Handle::Pid => "Pid",
117            // The read direction's four.
118            Handle::TryConstruct => "TryConstruct",
119            Handle::ReaderMark => "ReaderMark",
120            Handle::PidRead => "PidRead",
121            Handle::EmHeaderRead => "EmHeaderRead",
122        }))
123    }
124
125    fn unsigned_holder(bits: u16) -> Self::Type {
126        cpp_holder(bits)
127    }
128
129    /// A free function of the runtime's namespace.
130    ///
131    /// `zerodds::wire::no_dheader()` and not `Writer::no_dheader()`,
132    /// which is what this used to write. C++ has free functions, so an
133    /// operation the walk describes as having no receiver is one; and
134    /// the writer's qualification was wrong the moment a *reader* asked
135    /// for one — `Writer::pid_is_user_member` names a static member the
136    /// writer has no business declaring.
137    fn runtime_static(method: &str, args: Vec<Self::Expr>) -> Self::Expr {
138        cpp::Expr::Call {
139            callee: Box::new(cpp::Expr::Path(Self::wire(method))),
140            args,
141        }
142    }
143
144    /// A policy value, in the runtime's namespace.
145    fn constant(which: Constant) -> Self::Expr {
146        let (type_, value) = match which {
147            Constant::WcharWidth2 => ("WcharWidth", "Two"),
148            Constant::WcharWidth4 => ("WcharWidth", "Four"),
149            Constant::WcharFixed => ("WcharFraming", "Fixed"),
150            Constant::WcharGiopLengthPrefixed => ("WcharFraming", "GiopLengthPrefixed"),
151            Constant::WstringUnitsStream => ("WstringUnitOrder", "Stream"),
152            Constant::WstringUnitsBigEndianAlways => ("WstringUnitOrder", "BigEndianAlways"),
153            Constant::WstringUnitsByteOrderMark => ("WstringUnitOrder", "ByteOrderMark"),
154            Constant::WstringUnitsHostUnswapped => ("WstringUnitOrder", "HostUnswapped"),
155            Constant::WstringSpecification => ("WstringLengthPolicy", "Specification"),
156            Constant::WstringCharacterCount => ("WstringLengthPolicy", "CharacterCount"),
157            Constant::WstringCharacterCountTerminated => {
158                ("WstringLengthPolicy", "CharacterCountTerminated")
159            }
160            Constant::WstringSpecificationWideUnits => {
161                ("WstringLengthPolicy", "SpecificationWideUnits")
162            }
163            Constant::WstringRosTypeSupport => ("WstringLengthPolicy", "RosTypeSupport"),
164            Constant::WstringOctetsTerminated2 => ("WstringLengthPolicy", "OctetsTerminated2"),
165            Constant::WstringOctetsTerminated4 => ("WstringLengthPolicy", "OctetsTerminated4"),
166            Constant::WstringCharactersTerminated2 => {
167                ("WstringLengthPolicy", "CharactersTerminated2")
168            }
169            Constant::FixedDeclared => ("FixedDigitCount", "Declared"),
170            Constant::FixedValuePrecision => ("FixedDigitCount", "ValuePrecision"),
171            Constant::FixedValueStringDigits => ("FixedDigitCount", "ValueStringDigits"),
172            Constant::ParameterMember => ("ParameterRole", "Member"),
173            Constant::ParameterOptional => ("ParameterRole", "Optional"),
174            Constant::TryConstructDiscard => ("TryConstruct", "Discard"),
175            Constant::TryConstructTrim => ("TryConstruct", "Trim"),
176            Constant::TryConstructUseDefault => ("TryConstruct", "UseDefault"),
177            Constant::StatusOk => ("Status", "Ok"),
178            Constant::StatusDiscarded => ("Status", "Discarded"),
179            Constant::StatusImpossibleLength => ("Status", "ImpossibleLength"),
180            Constant::StatusOutOfRange => ("Status", "OutOfRange"),
181            Constant::StatusTruncated => ("Status", "Truncated"),
182        };
183        cpp::Expr::Path(Path::new(&["zerodds", "wire", type_, value]))
184    }
185
186    // -- expressions --------------------------------------------------
187
188    fn name(text: &str) -> Self::Expr {
189        cpp::Expr::Path(Path::new(&text.split("::").collect::<Vec<_>>()))
190    }
191
192    fn int(value: i128) -> Self::Expr {
193        cpp::Expr::Literal(Literal::int(value))
194    }
195
196    fn literal(value: Literal) -> Self::Expr {
197        cpp::Expr::Literal(value)
198    }
199
200    /// `strcmp` is the walk's name for a three-way code-unit
201    /// comparison and is not a name this column's strings answer to:
202    /// the key is a `std::string`, and `a.compare(b)` is
203    /// `char_traits<char>::compare` — the same unsigned octet order
204    /// C11 §7.24.4 gives `strcmp`, with the same sign convention.
205    fn call(callee: &str, args: Vec<Self::Expr>) -> Self::Expr {
206        if callee == "strcmp" {
207            let mut arguments = args.into_iter();
208            if let (Some(left), Some(right)) = (arguments.next(), arguments.next()) {
209                return cpp::Expr::MethodCall {
210                    receiver: Box::new(left),
211                    name: Ident::new("compare"),
212                    args: vec![right],
213                };
214            }
215            return cpp::Expr::Call {
216                callee: Box::new(Self::name("strcmp")),
217                args: Vec::new(),
218            };
219        }
220        cpp::Expr::Call {
221            callee: Box::new(Self::name(callee)),
222            args,
223        }
224    }
225
226    fn runtime(receiver: &str, method: &str, args: Vec<Self::Expr>) -> Self::Expr {
227        cpp::Expr::MethodCall {
228            receiver: Box::new(Self::name(receiver)),
229            name: Ident::new(method),
230            args,
231        }
232    }
233
234    /// `runtime/cpp` carries the three HDDS axes: `begin_packed_element`
235    /// and `finish_packed_element` on the writer and the reader, the
236    /// DHEADER pair for a sequence element's own length, and
237    /// `write_wstring_utf8_octets` / `read_wstring_utf8_octets`.
238    fn carries_hdds_axes() -> bool {
239        true
240    }
241
242    /// A reference, so a member is `.` and never `->`.
243    fn member(base: &str, field: &str) -> Self::Expr {
244        cpp::Expr::Member {
245            value: Box::new(Self::name(base)),
246            name: Ident::new(field),
247        }
248    }
249
250    fn whole(base: &str) -> Self::Expr {
251        Self::name(base)
252    }
253
254    /// `UINT64_C` is C's; C++ writes the suffix.
255    ///
256    /// −2^63 takes no suffix, because it is not a literal:
257    /// `-9223372036854775808LL` is a minus over a literal one past
258    /// `LLONG_MAX`. [`zerodds_lang_render::cpp::integer_text`] carries
259    /// the clause and the measurement and answers with the subtraction.
260    /// The result is a parenthesised expression rather than a literal,
261    /// which every site this reaches admits: the walk puts it inside a
262    /// `static_cast` in an `if` condition and never where C++ requires
263    /// a token — the generated union reader is a chain of comparisons
264    /// and carries no `case` label.
265    fn wide_int(value: i128) -> Self::Expr {
266        let text = if value > i128::from(i64::MAX) {
267            format!("{value}ULL")
268        } else if value == i128::from(i64::MIN) {
269            zerodds_lang_render::cpp::integer_text(value)
270        } else if value > i128::from(i32::MAX) || value < i128::from(i32::MIN) {
271            format!("{value}LL")
272        } else {
273            format!("{value}")
274        };
275        cpp::Expr::Literal(Literal::Int(text))
276    }
277
278    fn field_of(value: Self::Expr, field: &str) -> Self::Expr {
279        cpp::Expr::Member {
280            value: Box::new(value),
281            name: Ident::new(field),
282        }
283    }
284
285    /// The storage behind the accessor of a case (`adapter::cpp::union_storage`):
286    /// the codec reads and writes the storage and not the accessor, whose
287    /// setter selects the case.
288    fn case_member(base: &str, field: &str) -> Self::Expr {
289        Self::member(base, &crate::adapter::cpp::union_storage(field))
290    }
291
292    fn discriminant(base: &str) -> Self::Expr {
293        Self::member(base, crate::adapter::cpp::UNION_DISCRIMINATOR)
294    }
295
296    fn index(value: Self::Expr, at: Self::Expr) -> Self::Expr {
297        cpp::Expr::Index {
298            value: Box::new(value),
299            index: Box::new(at),
300        }
301    }
302
303    /// A `std::vector` and a `std::array` both answer `.size()`, so the
304    /// bound the materialiser fixed is not needed and not used.
305    fn count_of(base: &str, _fixed: Option<u64>) -> Self::Expr {
306        cpp::Expr::MethodCall {
307            receiver: Box::new(Self::name(base)),
308            name: Ident::new("size"),
309            args: Vec::new(),
310        }
311    }
312
313    fn binary(op: &'static str, lhs: Self::Expr, rhs: Self::Expr) -> Self::Expr {
314        cpp::Expr::Binary {
315            op,
316            lhs: Box::new(lhs),
317            rhs: Box::new(rhs),
318        }
319    }
320
321    /// The tree has no unary node; `!a` is `a == false` and `-a` is
322    /// `0 - a`, which is what both mean.
323    /// `!a` as `a == false`, and every other operator as itself.
324    ///
325    /// The negation is spelled out because `!` on a value the walk
326    /// hands over is `operator!` on whatever type it has, and `== false`
327    /// says the same thing for a `bool` without depending on that. The
328    /// rest go through [`cpp::Expr::Unary`], which is what a complement
329    /// needs: the form this used before wrote `0 ~ a`, which no
330    /// compiler reads.
331    fn unary(op: &'static str, value: Self::Expr) -> Self::Expr {
332        match op {
333            "!" => cpp::Expr::Binary {
334                op: "==",
335                lhs: Box::new(value),
336                rhs: Box::new(cpp::Expr::Literal(Literal::Bool(false))),
337            },
338            _ => cpp::Expr::Unary {
339                op,
340                value: Box::new(value),
341            },
342        }
343    }
344
345    /// The bracket is written, because the walk asked for it.
346    ///
347    /// This returned the expression unchanged, on the reading that "the
348    /// renderer brackets by precedence". It does not — `zerodds-lang-cst`
349    /// says so in as many words, on `Expr::Paren`: "this tree holds no
350    /// operator precedence, so a renderer walking it cannot know where a
351    /// bracket is needed. A builder that needs one says so."
352    ///
353    /// What that cost: a bitmask's reader asks for
354    /// `paren(zd_bits & ~UINT64_C(131)) != 0` and got
355    /// `zd_bits & ~UINT64_C(131) != 0`, which C++ groups as
356    /// `zd_bits & (~131 != 0)` — a different number, and one that would
357    /// have accepted every undeclared bit. `-Wparentheses -Werror` made
358    /// it a build failure rather than a wrong value, which is the only
359    /// reason it was found at the compiler and not in a byte comparison.
360    fn paren(value: Self::Expr) -> Self::Expr {
361        cpp::Expr::Paren(Box::new(value))
362    }
363
364    fn cast(type_: Self::Type, value: Self::Expr) -> Self::Expr {
365        cpp::Expr::Cast {
366            kind: cpp::CastKind::Static,
367            type_: Box::new(type_),
368            value: Box::new(value),
369        }
370    }
371
372    /// A reference takes the value either way: there is no address to
373    /// take and no array to decay.
374    fn handoff(value: Self::Expr, _form: Handoff) -> Self::Expr {
375        value
376    }
377
378    /// `write_char` takes a `char32_t`, so the code point is a cast to
379    /// it and the runtime keeps what the width allows.
380    fn code_point(value: Self::Expr, _narrow: bool) -> Self::Expr {
381        cpp::Expr::Cast {
382            kind: cpp::CastKind::Static,
383            type_: Box::new(cpp::Type::named(Path::new(&["char32_t"]))),
384            value: Box::new(value),
385        }
386    }
387
388    /// The integer form is two `static_cast`s — through the unsigned
389    /// type of the source's width first, which is what masks a negative
390    /// value to its two's-complement octets instead of sign-extending
391    /// it — and the float forms are the runtime's `bit_pattern_f32` and
392    /// `bit_pattern_f64`, because C++ has no expression that reads a
393    /// float's bits without `std::memcpy`.
394    fn bit_pattern(source: BitPatternSource, value: Self::Expr) -> Self::Expr {
395        let through = |narrow: &str, value: cpp::Expr| cpp::Expr::Cast {
396            kind: cpp::CastKind::Static,
397            type_: Box::new(cpp::Type::named(Path::new(&["std", "uint64_t"]))),
398            value: Box::new(cpp::Expr::Cast {
399                kind: cpp::CastKind::Static,
400                type_: Box::new(cpp::Type::named(Path::new(&["std", narrow]))),
401                value: Box::new(value),
402            }),
403        };
404        match source {
405            BitPatternSource::Int { octets } => {
406                let narrow = match octets {
407                    1 => "uint8_t",
408                    2 => "uint16_t",
409                    4 => "uint32_t",
410                    _ => "uint64_t",
411                };
412                through(narrow, value)
413            }
414            BitPatternSource::Bool => cpp::Expr::Cast {
415                kind: cpp::CastKind::Static,
416                type_: Box::new(cpp::Type::named(Path::new(&["std", "uint64_t"]))),
417                value: Box::new(value),
418            },
419            BitPatternSource::Float32 => Self::runtime_static("bit_pattern_f32", vec![value]),
420            BitPatternSource::Float64 => Self::runtime_static("bit_pattern_f64", vec![value]),
421        }
422    }
423
424    // -- statements ---------------------------------------------------
425
426    fn declare(type_: Self::Type, name: &str, value: Option<Self::Expr>) -> Self::Stmt {
427        cpp::Statement::Declare {
428            type_,
429            name: Ident::new(name),
430            value,
431        }
432    }
433
434    fn perform(value: Self::Expr) -> Self::Stmt {
435        cpp::Statement::Expr(value)
436    }
437
438    fn give_back(value: Option<Self::Expr>) -> Self::Stmt {
439        cpp::Statement::Return(value)
440    }
441
442    fn when(
443        condition: Self::Expr,
444        then: Self::Block,
445        otherwise: Option<Self::Block>,
446    ) -> Self::Stmt {
447        cpp::Statement::If {
448            condition,
449            then,
450            otherwise,
451        }
452    }
453
454    /// `for (std::size_t i = 0; i < bound; i = i + 1)`, the step written
455    /// out for the reason C's is: the tree has no increment node, and
456    /// one spelling across the backends keeps two columns' generated
457    /// source comparable by eye when their octets disagree.
458    fn count_up(counter: &str, bound: Self::Expr, body: Self::Block) -> Self::Stmt {
459        cpp::Statement::For {
460            init: Box::new(cpp::Statement::Declare {
461                type_: cpp::Type::named(Path::new(&["std", "size_t"])),
462                name: Ident::new(counter),
463                value: Some(cpp::Expr::Literal(Literal::int(0))),
464            }),
465            condition: cpp::Expr::Binary {
466                op: "<",
467                lhs: Box::new(Self::name(counter)),
468                rhs: Box::new(bound),
469            },
470            step: cpp::Expr::Binary {
471                op: "=",
472                lhs: Box::new(Self::name(counter)),
473                rhs: Box::new(cpp::Expr::Binary {
474                    op: "+",
475                    lhs: Box::new(Self::name(counter)),
476                    rhs: Box::new(cpp::Expr::Literal(Literal::int(1))),
477                }),
478            },
479            body,
480        }
481    }
482
483    fn until(condition: Self::Expr, body: Self::Block) -> Self::Stmt {
484        cpp::Statement::While { condition, body }
485    }
486
487    fn leave() -> Self::Stmt {
488        cpp::Statement::Break
489    }
490
491    fn note(doc: Doc) -> Self::Stmt {
492        cpp::Statement::Comment(doc)
493    }
494
495    /// `v.x != nullptr`. XTypes 1.3 §7.5.1.2.4.2: "A NULL pointer shall
496    /// indicate an omitted value."
497    fn present(base: &str, field: &str) -> Self::Expr {
498        cpp::Expr::Binary {
499            op: "!=",
500            lhs: Box::new(Self::member(base, field)),
501            rhs: Box::new(Self::name("nullptr")),
502        }
503    }
504
505    /// Nothing on the way in — [`Cpp::make_indirection`] makes the object
506    /// — and `zerodds::wire::destroy(v.x);` on the way out, which deletes
507    /// what the member held and leaves it NULL.
508    fn set_present(base: &str, field: &str, present: bool) -> Vec<Self::Stmt> {
509        if present {
510            return Vec::new();
511        }
512        vec![cpp::Statement::Expr(cpp::Expr::Call {
513            callee: Box::new(cpp::Expr::Path(Self::wire("destroy"))),
514            args: vec![Self::member(base, field)],
515        })]
516    }
517
518    /// An `@optional` member is a plain pointer (§7.5.1.2.4.2).
519    fn optionals_are_pointers() -> bool {
520        true
521    }
522
523    /// A `std::string` is a value, so an `@optional string` is a
524    /// `std::string *` like any other.
525    fn value_is_pointer(_ast: &Ast, _id: Id<TypeNode>) -> bool {
526        false
527    }
528
529    /// `if (!(v.x != nullptr)) { throw zerodds::wire::UnsetExternal(...); }`
530    fn refuse_unset_external(present: Self::Expr) -> Vec<Self::Stmt> {
531        vec![cpp::Statement::If {
532            condition: cpp::Expr::Unary {
533                op: "!",
534                value: Box::new(cpp::Expr::Paren(Box::new(present.clone()))),
535            },
536            then: cpp::Block {
537                statements: vec![cpp::Statement::Throw(cpp::Expr::Construct {
538                    type_: Box::new(cpp::Type::named(Self::wire("UnsetExternal"))),
539                    args: vec![
540                        cpp::Expr::MethodCall {
541                            receiver: Box::new(Self::name("w")),
542                            name: Ident::new("position"),
543                            args: Vec::new(),
544                        },
545                        cpp::Expr::Literal(Literal::string(&cpp_text_of(&present))),
546                    ],
547                    braced: false,
548                })],
549            },
550            otherwise: None,
551        }]
552    }
553
554    /// The vector is the elements.
555    /// `.value()` and not `*`: the two are the same inside a guarded
556    /// branch and the first says which one it is.
557    /// A `std::pair`, so `first` and `second`.
558    fn entry_half(entry: Self::Expr, key: bool) -> Self::Expr {
559        cpp::Expr::Member {
560            value: Box::new(entry),
561            name: Ident::new(if key { "first" } else { "second" }),
562        }
563    }
564
565    /// The member, the pointer; [`Cpp::behind_indirection`] dereferences it.
566    fn optional_value(_ast: &Ast, _id: Id<TypeNode>, base: &str, field: &str) -> Self::Expr {
567        Self::member(base, field)
568    }
569
570    /// `*place` — the object an optional pointer points at.
571    fn behind_indirection(_ast: &Ast, _id: Id<TypeNode>, place: Self::Expr) -> Self::Expr {
572        cpp::Expr::Deref(Box::new(place))
573    }
574
575    /// `zerodds::wire::pointer_slot(place)` — the pointer made to point at
576    /// a new object when it points nowhere, and the object.
577    fn make_indirection(
578        _ast: &Ast,
579        _id: Id<TypeNode>,
580        place: Self::Expr,
581    ) -> (Vec<Self::Stmt>, Self::Expr) {
582        (
583            Vec::new(),
584            cpp::Expr::Call {
585                callee: Box::new(cpp::Expr::Path(Self::wire("pointer_slot"))),
586                args: vec![place],
587            },
588        )
589    }
590
591    /// `true`: an `@external` member is a `std::shared_ptr`, and a member on a
592    /// cycle needs the same: `std::optional<T>` and a plain `T` both want
593    /// `T` complete.
594    /// [`crate::adapter::held_externally`] is the rule the adapter and
595    /// the defaulter ask as well.
596    fn holds_a_cycle_externally() -> bool {
597        true
598    }
599
600    /// `*v.x` — the shared pointer dereferenced, for `@external` with
601    /// and without `@optional` alike: the member's codec takes the
602    /// value, and `crates/lang-rules/src/adapter/cpp.rs` gives an
603    /// `@external` member one indirection and no `std::optional` around
604    /// it.
605    fn external_value(
606        _ast: &Ast,
607        _id: Id<TypeNode>,
608        base: &str,
609        field: &str,
610        _optional: bool,
611    ) -> Self::Expr {
612        cpp::Expr::Unary {
613            op: "*",
614            value: Box::new(Self::member(base, field)),
615        }
616    }
617
618    /// `zerodds::wire::external_slot(v.x)` — the slot filled and the
619    /// place handed back, in one expression.
620    ///
621    /// The counterpart of [`Cpp::optional_destination`]'s `emplace`: a
622    /// pointer member has nothing to write into until it points at
623    /// something. A function of the runtime and not
624    /// `std::make_shared<T>()` written here, because this seam is handed
625    /// the member and not its type.
626    fn external_destination(
627        base: &str,
628        field: &str,
629        _optional: bool,
630    ) -> (Vec<Self::Stmt>, Self::Expr) {
631        (Vec::new(), Self::external_slot(Self::member(base, field)))
632    }
633
634    /// `*v.x` for a branch too: a union arm's `@external` field is the
635    /// same `std::shared_ptr<T>` a structure member is.
636    fn external_case_value(_ast: &Ast, _id: Id<TypeNode>, base: &str, field: &str) -> Self::Expr {
637        Self::external_value(
638            _ast,
639            _id,
640            base,
641            &crate::adapter::cpp::union_storage(field),
642            false,
643        )
644    }
645
646    /// The same allocation and the same place as
647    /// [`Cpp::external_destination`].
648    fn external_case_destination(base: &str, field: &str) -> (Vec<Self::Stmt>, Self::Expr) {
649        Self::external_destination(base, &crate::adapter::cpp::union_storage(field), false)
650    }
651
652    /// `v.x != nullptr`: the pointer is the absence, with no
653    /// `std::optional` around it to carry a second one.
654    fn external_present(base: &str, field: &str) -> Self::Expr {
655        cpp::Expr::Binary {
656            op: "!=",
657            lhs: Box::new(Self::member(base, field)),
658            rhs: Box::new(Self::name("nullptr")),
659        }
660    }
661
662    /// Nothing on the way in — [`Cpp::external_destination`] allocates —
663    /// and `v.x = nullptr;` on the way out.
664    fn external_set_present(base: &str, field: &str, present: bool) -> Vec<Self::Stmt> {
665        if present {
666            return Vec::new();
667        }
668        vec![cpp::Statement::Expr(cpp::Expr::Binary {
669            op: "=",
670            lhs: Box::new(Self::member(base, field)),
671            rhs: Box::new(Self::name("nullptr")),
672        })]
673    }
674
675    /// `v[i]`, except for the one container C++ specialises.
676    ///
677    /// `std::vector<bool>` packs its elements into bits — [vector.bool] —
678    /// and `operator[]` hands back a proxy object rather than a
679    /// `bool &`. A generated decoder takes its destination by reference,
680    /// so there is nothing for it to point at, and the call does not
681    /// compile. Read into a local and assign it back: one statement
682    /// either side, and only where the element really is `bool`.
683    ///
684    /// Not solved by changing what a `sequence<boolean>` maps to. The
685    /// element codec is named by the IDL kind — `zd_dec_p_boolean` —
686    /// takes a `bool &`, and is the same function whether the value is a
687    /// member, an array element or a map's value, so a container holding
688    /// anything but `bool` would not match it either. The proxy is the
689    /// container's, so the work-around belongs at the container.
690    fn sequence_element(
691        base: &str,
692        index: Self::Expr,
693        element: Self::Type,
694    ) -> (Vec<Self::Stmt>, Self::Expr, Vec<Self::Stmt>) {
695        let slot = cpp::Expr::Index {
696            value: Box::new(Self::elements(base)),
697            index: Box::new(index),
698        };
699        if element != cpp::Type::Primitive(cpp::Primitive::Bool) {
700            return (Vec::new(), slot, Vec::new());
701        }
702        let local = "zd_bit";
703        (
704            vec![cpp::Statement::Declare {
705                type_: cpp::Type::Primitive(cpp::Primitive::Bool),
706                name: Ident::new(local),
707                value: Some(cpp::Expr::Literal(Literal::Bool(false))),
708            }],
709            Self::name(local),
710            vec![cpp::Statement::Expr(cpp::Expr::Binary {
711                op: "=",
712                lhs: Box::new(slot),
713                rhs: Box::new(Self::name(local)),
714            })],
715        )
716    }
717
718    /// `v.resize(count);`, which is `make_room` again in the shortening
719    /// direction. A `std::vector` owns its elements, so the ones past
720    /// the bound are destroyed rather than left where C leaves them.
721    fn keep_first(base: &str, count: Self::Expr) -> Vec<Self::Stmt> {
722        vec![cpp::Statement::Expr(cpp::Expr::MethodCall {
723            receiver: Box::new(Self::name(base)),
724            name: Ident::new("resize"),
725            args: vec![count],
726        })]
727    }
728
729    /// The member: [`Cpp::make_indirection`] makes it point somewhere.
730    fn optional_destination(base: &str, field: &str) -> (Vec<Self::Stmt>, Self::Expr) {
731        (Vec::new(), Self::member(base, field))
732    }
733
734    fn elements(base: &str) -> Self::Expr {
735        Self::name(base)
736    }
737
738    /// `w.write_octets(v.data(), n)`: `std::vector<std::uint8_t>` and
739    /// `std::array<std::uint8_t, N>` both hold their octets contiguously.
740    fn bulk_octets_write(base: &str, count: Self::Expr) -> Option<Self::Stmt> {
741        Some(Self::perform(Self::runtime(
742            "w",
743            "write_octets",
744            vec![data_of(base), count],
745        )))
746    }
747
748    /// `r.read_octets(v.data(), n)`, after `make_room` has resized `v`.
749    fn bulk_octets_read(base: &str, count: Self::Expr) -> Option<Self::Stmt> {
750        Some(Self::perform(Self::runtime(
751            "r",
752            "read_octets",
753            vec![data_of(base), count],
754        )))
755    }
756
757    /// One statement: `resize` throws rather than returning nothing, so
758    /// there is no guard and the fill follows unconditionally.
759    fn make_room(
760        base: &str,
761        _element: Self::Type,
762        count: Self::Expr,
763        // C's slot is storage the moment the buffer is there, so
764        // whether the fill assigns it or writes into it makes no
765        // difference to what has to be allocated.
766        _assigned_whole: bool,
767        fill: Vec<Self::Stmt>,
768    ) -> Vec<Self::Stmt> {
769        let mut out = vec![cpp::Statement::Expr(cpp::Expr::MethodCall {
770            receiver: Box::new(Self::name(base)),
771            name: Ident::new("resize"),
772            args: vec![count],
773        })];
774        out.extend(fill);
775        out
776    }
777
778    fn absent() -> Self::Expr {
779        cpp::Expr::Path(Path::new(&["nullptr"]))
780    }
781
782    /// Nothing to check. Every value this column's reader materialises
783    /// is a `std::string` or a `std::vector`, which own their storage
784    /// and are never absent; the read either produced one or threw.
785    fn materialised(_value: Self::Expr) -> Option<Self::Expr> {
786        None
787    }
788
789    // -- assembly -----------------------------------------------------
790
791    fn block(statements: Vec<Self::Stmt>) -> Self::Block {
792        cpp::Block { statements }
793    }
794
795    fn into_statements(block: Self::Block) -> Vec<Self::Stmt> {
796        block.statements
797    }
798
799    /// `static void zd_enc_T(zerodds::wire::Writer &w, const T &v)`.
800    ///
801    /// A prototype, unlike Java's, is emitted: C++ resolves a call to a
802    /// free function by what is declared above it, so a recursive or
803    /// mutually recursive type needs the declaration ahead of the body
804    /// exactly as C does.
805    fn routine(spec: Routine<Self>) -> Option<Self::Decl> {
806        let stream = cpp::Param {
807            type_: cpp::Type::Ref(Box::new(cpp::Type::named(Self::wire(if spec.writing {
808                "Writer"
809            } else {
810                "Reader"
811            })))),
812            name: Some(Ident::new(if spec.writing { "w" } else { "r" })),
813            default: None,
814        };
815        let mut params = vec![
816            stream,
817            cpp::Param {
818                // `const` in the written direction and not in the read
819                // one, the same distinction C makes with its pointer.
820                type_: cpp::Type::Ref(Box::new(if spec.writing {
821                    cpp::Type::Const(Box::new(spec.sample))
822                } else {
823                    spec.sample
824                })),
825                name: Some(Ident::new("v")),
826                default: None,
827            },
828        ];
829        params.extend(spec.extra.into_iter().map(|(type_, name)| cpp::Param {
830            type_,
831            name: Some(Ident::new(&name)),
832            default: None,
833        }));
834        // `(void)v;` and `(void)w;` ahead of the body, for the reason
835        // the C reader writes `(void)zd_tc;`: an aggregate with no
836        // members has nothing to write, and `-Wunused-parameter
837        // -Werror` fails the build on the one case that is legal C++
838        // and empty. Emitted unconditionally rather than from a guess
839        // about which bodies touch which parameter.
840        //
841        // The stream was left out of that until an empty `@appendable`
842        // structure was read under XCDR1, where the type takes no
843        // delimiter either — so the body was empty in both directions
844        // and neither parameter was touched. The C column cannot reach
845        // it: C23 §6.7.2.1p8 leaves a structure with no named member
846        // undefined and the C materialiser refuses one.
847        let stream = if spec.writing { "w" } else { "r" };
848        let body = spec.body.map(|block| {
849            let mut statements = vec![
850                cpp::Statement::Expr(cpp::Expr::Cast {
851                    kind: cpp::CastKind::Static,
852                    type_: Box::new(cpp::Type::Void),
853                    value: Box::new(Self::name(stream)),
854                }),
855                cpp::Statement::Expr(cpp::Expr::Cast {
856                    kind: cpp::CastKind::Static,
857                    type_: Box::new(cpp::Type::Void),
858                    value: Box::new(Self::name("v")),
859                }),
860            ];
861            statements.extend(Self::into_statements(block));
862            Self::block(statements)
863        });
864        Some(cpp::Decl::Function(cpp::Function {
865            doc: spec.doc,
866            returns: cpp::Type::Void,
867            name: Ident::new(&spec.name),
868            params,
869            is_inline: false,
870            is_static: true,
871            is_constexpr: false,
872            is_noexcept: false,
873            // `CodecTarget::routine`'s `tolerate_unused`, which
874            // `crate::emit::encode::maybe_unused` sets for the one codec
875            // that may have no caller: a `wchar` whose element form is
876            // written inline under TAO's framing. g++ refused the probe
877            // with `-Werror=unused-function` on `zd_enc_p_wchar` in 48
878            // matrix cells (TAO dialect, every case with a `wchar` run).
879            is_maybe_unused: spec.tolerate_unused,
880            // A body absent is a declaration and not an empty one: C++
881            // resolves a call to a free function by what is declared
882            // above it.
883            body: body.map_or(cpp::FunctionBody::None, cpp::FunctionBody::Block),
884        }))
885    }
886
887    // -- the materialiser's boundary ----------------------------------
888
889    fn value_type(ast: &Ast, id: Id<TypeNode>) -> Self::Type {
890        cpp_value_type(ast, id, "codec").unwrap_or_else(|_| {
891            cpp::Type::named(Path::new(
892                &[format!("UnresolvedType{}", id.slot()).as_str()],
893            ))
894        })
895    }
896
897    fn type_name(ast: &Ast, id: Id<TypeNode>) -> String {
898        cpp_type_tag(ast, id)
899    }
900
901    fn escape(name: &str) -> String {
902        cxx_name(name)
903    }
904
905    fn map_entry_name(ast: &Ast, id: Id<TypeNode>) -> String {
906        cpp_type_tag(ast, id)
907    }
908
909    /// `std::pair<K, V>` — the type the materialiser spells the entry
910    /// as. The tag `map_entry_name` answers is a *name* for generated
911    /// identifiers (`zd_dec_map_string_long`), not a declared type, so
912    /// the default's `named(tag)` declared the scratch entry with an
913    /// identifier no compiler finds.
914    fn map_entry_type(ast: &Ast, id: Id<TypeNode>) -> Self::Type {
915        let Ok(zerodds_ast::TypeKind::Map { key, value, .. }) =
916            ast.type_(id).map(|node| node.kind.clone())
917        else {
918            return Self::named(&Self::map_entry_name(ast, id));
919        };
920        let at = cpp_type_tag(ast, id);
921        let half = |half: Option<Id<TypeNode>>| {
922            half.and_then(|id| cpp_value_type(ast, id, &at).ok())
923                .unwrap_or(cpp::Type::Void)
924        };
925        cpp::Type::template(
926            Path::new(&["std", "pair"]),
927            vec![half(key.resolved()), half(value.resolved())],
928        )
929    }
930
931    /// C++ has namespaces, so a module needs no prefix in a name.
932    fn module_prefix(_ast: &Ast, _id: Id<TypeNode>) -> String {
933        String::new()
934    }
935
936    /// `::a::b::Colour::red`.
937    ///
938    /// The enumeration's own name is in the middle, which is what makes
939    /// this a seam rather than a prefix and a bare identifier:
940    /// `adapter/cpp.rs` declares a **scoped** enumeration, so an
941    /// enumerator is a member of the type. Its reason is written there —
942    /// an unscoped `enum` would put every enumerator of every
943    /// enumeration of a module in one namespace, where two IDL-legal
944    /// names collide.
945    fn enumerator_constant(ast: &Ast, id: Id<TypeNode>, name: &str) -> Self::Expr {
946        Self::name(&enumerator_constant_name(ast, id, name))
947    }
948
949    /// A call to the generated defaulter, as the C column does it.
950    ///
951    /// Not `place = T{}`. Value-initialisation is not Table 9 — an
952    /// enumeration whose first literal is not zero, a union's selected
953    /// branch and a member's own `@default` are three rows it gets wrong
954    /// — and two columns answering the same table by different
955    /// mechanisms is a divergence no byte comparison shows, because both
956    /// produce *a* value. [`crate::default_cpp`] carries the whole
957    /// argument.
958    ///
959    /// # Errors
960    /// Never here. The defaulter is a call, and whether the type has one
961    /// is settled where the function is built —
962    /// [`crate::default_cpp::cpp_defaulters_called_by`] reads the calls
963    /// this emitted and refuses there, by name.
964    fn default_into(
965        ast: &Ast,
966        id: Id<TypeNode>,
967        place: Self::Expr,
968    ) -> Result<Self::Stmt, EncodeError> {
969        Ok(crate::default_cpp::default_into(ast, id, place))
970    }
971
972    /// # Errors
973    /// [`EncodeError::NoWriter`] where a `@default` names a value C++
974    /// cannot spell — a `long double` literal is the one the corpus
975    /// reaches.
976    fn member_default(
977        ast: &Ast,
978        node: &MemberNode,
979        type_: Id<TypeNode>,
980        place: Self::Expr,
981    ) -> Result<Vec<Self::Stmt>, EncodeError> {
982        crate::default_cpp::member_default(ast, node, type_, place)
983    }
984}
985
986/// The scoped name of one enumerator, as text.
987///
988/// The enumeration's own name is in the middle, which is what makes
989/// this more than a prefix and a bare identifier: `adapter/cpp.rs`
990/// declares a **scoped** enumeration, so an enumerator is a member of
991/// the type. `default_cpp` builds a path from this, and assembling the
992/// name a second time in that file would be a second place the C++
993/// spelling is decided.
994pub fn enumerator_constant_name(ast: &Ast, id: Id<TypeNode>, name: &str) -> String {
995    let mut path = cpp_type_path(ast, id)
996        .segments()
997        .iter()
998        .map(|segment| segment.as_str().to_string())
999        .collect::<Vec<_>>()
1000        .join("::");
1001    path.push_str("::");
1002    path.push_str(&cxx_name(name));
1003    format!("::{path}")
1004}
1005
1006/// `base.data()`: the first element of a contiguous collection.
1007fn data_of(base: &str) -> cpp::Expr {
1008    cpp::Expr::MethodCall {
1009        receiver: Box::new(Cpp::name(base)),
1010        name: Ident::new("data"),
1011        args: Vec::new(),
1012    }
1013}
1014
1015/// The member's name in a presence test the walk built: `v.x != nullptr`
1016/// gives `x`. Only for the text of a diagnostic.
1017fn cpp_text_of(present: &cpp::Expr) -> String {
1018    if let cpp::Expr::Binary { lhs, .. } = present {
1019        if let cpp::Expr::Member { name, .. } = lhs.as_ref() {
1020            return name.as_str().to_string();
1021        }
1022    }
1023    String::new()
1024}